diff --git a/TestApp/MyService.cs b/TestApp/MyService.cs index 7c0efdf..2c35734 100644 --- a/TestApp/MyService.cs +++ b/TestApp/MyService.cs @@ -18,13 +18,6 @@ namespace TestApp protected override void StartAction() { - var client = Exchange.GetConnection("192.168.51.104:49672"); - client?.Request("__service_description__", record => - { - Log.Info(record.ServiceInfo.ServiceKey); - }); - return; - Log.Info("Started"); ReadServiceInfo(); var host = UseHost(8800); diff --git a/ZeroLevel/Services/AsService/Builder/EnvironmentBuilder.cs b/ZeroLevel/Services/AsService/Builder/EnvironmentBuilder.cs new file mode 100644 index 0000000..21e3429 --- /dev/null +++ b/ZeroLevel/Services/AsService/Builder/EnvironmentBuilder.cs @@ -0,0 +1,7 @@ +namespace ZeroLevel.Services.AsService.Builder +{ + public interface EnvironmentBuilder + { + HostEnvironment Build(); + } +} diff --git a/ZeroLevel/Services/AsService/Builder/HostBuilder.cs b/ZeroLevel/Services/AsService/Builder/HostBuilder.cs new file mode 100644 index 0000000..c53a788 --- /dev/null +++ b/ZeroLevel/Services/AsService/Builder/HostBuilder.cs @@ -0,0 +1,15 @@ +using System; + +namespace ZeroLevel.Services.AsService.Builder +{ + public interface HostBuilder + { + HostEnvironment Environment { get; } + HostSettings Settings { get; } + + Host Build(ServiceBuilder serviceBuilder); + + void Match(Action callback) + where T : class, HostBuilder; + } +} diff --git a/ZeroLevel/Services/AsService/Builder/ServiceBuilder.cs b/ZeroLevel/Services/AsService/Builder/ServiceBuilder.cs new file mode 100644 index 0000000..baaf5cb --- /dev/null +++ b/ZeroLevel/Services/AsService/Builder/ServiceBuilder.cs @@ -0,0 +1,7 @@ +namespace ZeroLevel.Services.AsService.Builder +{ + public interface ServiceBuilder + { + ServiceHandle Build(HostSettings settings); + } +} diff --git a/ZeroLevel/Services/AsService/Credentials.cs b/ZeroLevel/Services/AsService/Credentials.cs new file mode 100644 index 0000000..a87afad --- /dev/null +++ b/ZeroLevel/Services/AsService/Credentials.cs @@ -0,0 +1,16 @@ +namespace ZeroLevel.Services.AsService +{ + public class Credentials + { + public Credentials(string username, string password, ServiceAccount account) + { + Username = username; + Account = account; + Password = password; + } + + public string Username { get; } + public string Password { get; } + public ServiceAccount Account { get; } + } +} diff --git a/ZeroLevel/Services/AsService/Host.cs b/ZeroLevel/Services/AsService/Host.cs new file mode 100644 index 0000000..20241c8 --- /dev/null +++ b/ZeroLevel/Services/AsService/Host.cs @@ -0,0 +1,25 @@ +namespace ZeroLevel.Services.AsService +{ + public enum ExitCode + { + Ok = 0, + ServiceAlreadyInstalled = 1242, + ServiceNotInstalled = 1243, + ServiceAlreadyRunning = 1056, + ServiceNotRunning = 1062, + ServiceControlRequestFailed = 1064, + AbnormalExit = 1067, + SudoRequired = 2, + NotRunningOnWindows = 11 + } + /// + /// A Host can be a number of configured service hosts, from installers to service runners + /// + public interface Host + { + /// + /// Runs the configured host + /// + ExitCode Run(); + } +} diff --git a/ZeroLevel/Services/AsService/HostConfigurators/Configurator.cs b/ZeroLevel/Services/AsService/HostConfigurators/Configurator.cs new file mode 100644 index 0000000..c8766dd --- /dev/null +++ b/ZeroLevel/Services/AsService/HostConfigurators/Configurator.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace ZeroLevel.Services.AsService +{ + public interface Configurator + { + IEnumerable Validate(); + } +} diff --git a/ZeroLevel/Services/AsService/HostConfigurators/HostBuilderConfigurator.cs b/ZeroLevel/Services/AsService/HostConfigurators/HostBuilderConfigurator.cs new file mode 100644 index 0000000..69774db --- /dev/null +++ b/ZeroLevel/Services/AsService/HostConfigurators/HostBuilderConfigurator.cs @@ -0,0 +1,15 @@ +using ZeroLevel.Services.AsService.Builder; + +namespace ZeroLevel.Services.AsService +{ + public interface HostBuilderConfigurator : + Configurator + { + /// + /// Configures the host builder. + /// + /// The host builder. + /// The configured host builder. + HostBuilder Configure(HostBuilder builder); + } +} diff --git a/ZeroLevel/Services/AsService/HostConfigurators/HostConfigurator.cs b/ZeroLevel/Services/AsService/HostConfigurators/HostConfigurator.cs new file mode 100644 index 0000000..53bfb55 --- /dev/null +++ b/ZeroLevel/Services/AsService/HostConfigurators/HostConfigurator.cs @@ -0,0 +1,128 @@ +using System; + +namespace ZeroLevel.Services.AsService +{ + public interface HostConfigurator + { + /// + /// Specifies the name of the service as it should be displayed in the service control manager + /// + /// + void SetDisplayName(string name); + + /// + /// Specifies the name of the service as it is registered in the service control manager + /// + /// + void SetServiceName(string name); + + /// + /// Specifies the description of the service that is displayed in the service control manager + /// + /// + void SetDescription(string description); + + /// + /// Specifies the service instance name that should be used when the service is registered + /// + /// + void SetInstanceName(string instanceName); + + /// + /// Sets the amount of time to wait for the service to start before timing out. Default is 10 seconds. + /// + /// + void SetStartTimeout(TimeSpan startTimeOut); + + /// + /// Sets the amount of time to wait for the service to stop before timing out. Default is 10 seconds. + /// + /// + void SetStopTimeout(TimeSpan stopTimeOut); + + /// + /// Enable pause and continue support for the service (default is disabled) + /// + void EnablePauseAndContinue(); + + /// + /// Enable support for service shutdown (signaled by the host OS) + /// + void EnableShutdown(); + + /// + /// Enables support for the session changed event + /// + void EnableSessionChanged(); + + /// + /// Enables support for power events (signaled by the host OS) + /// + void EnablePowerEvents(); + + /// + /// Specifies the builder factory to use when the service is invoked + /// + /// + void UseHostBuilder(HostBuilderFactory hostBuilderFactory); + + /// + /// Sets the service builder to use for creating the service + /// + /// + void UseServiceBuilder(ServiceBuilderFactory serviceBuilderFactory); + + /// + /// Sets the environment builder to use for creating the service (defaults to Windows) + /// + /// + void UseEnvironmentBuilder(EnvironmentBuilderFactory environmentBuilderFactory); + + /// + /// Adds a a configurator for the host builder to the configurator + /// + /// + void AddConfigurator(HostBuilderConfigurator configurator); + + /// + /// Parses the command line options and applies them to the host configurator + /// + void ApplyCommandLine(); + + /// + /// Parses the command line options from the specified command line and applies them to the host configurator + /// + /// + void ApplyCommandLine(string commandLine); + + /// + /// Adds a command line switch (--name) that can be either true or false. Switches are CASE SeNsITiVe + /// + /// The name of the switch, as it will appear on the command line + /// + void AddCommandLineSwitch(string name, Action callback); + + /// + /// Adds a command line definition (-name:value) that can be specified. the name is case sensitive. If the + /// definition + /// + /// + /// + void AddCommandLineDefinition(string name, Action callback); + + /// + /// Specifies a callback to be run when Topshelf encounters an exception while starting, running + /// or stopping. This callback does not replace Topshelf's default handling of any exceptions, and + /// is intended to allow for local cleanup, logging, etc. This is not required, and is only invoked + /// if a callback is provided. + /// + /// The action to run when an exception occurs. + void OnException(Action callback); + + /// + /// The policy that will be used when Topself detects an UnhandledException in the + /// application. The default policy is to log an error and to stop the service. + /// + UnhandledExceptionPolicyCode UnhandledExceptionPolicy { get; set; } + } +} diff --git a/ZeroLevel/Services/AsService/HostConfigurators/ValidateResult.cs b/ZeroLevel/Services/AsService/HostConfigurators/ValidateResult.cs new file mode 100644 index 0000000..f05b0f7 --- /dev/null +++ b/ZeroLevel/Services/AsService/HostConfigurators/ValidateResult.cs @@ -0,0 +1,36 @@ +using System; + +namespace ZeroLevel.Services.AsService +{ + [Serializable] + public enum ValidationResultDisposition + { + Success, + Warning, + Failure, + } + + public interface ValidateResult + { + /// + /// The disposition of the result, any Failure items will prevent + /// the configuration from completing. + /// + ValidationResultDisposition Disposition { get; } + + /// + /// The message associated with the result + /// + string Message { get; } + + /// + /// The key associated with the result (chained if configurators are nested) + /// + string Key { get; } + + /// + /// The value associated with the result + /// + string Value { get; } + } +} diff --git a/ZeroLevel/Services/AsService/HostControl.cs b/ZeroLevel/Services/AsService/HostControl.cs new file mode 100644 index 0000000..e94f713 --- /dev/null +++ b/ZeroLevel/Services/AsService/HostControl.cs @@ -0,0 +1,26 @@ +using System; + +namespace ZeroLevel.Services.AsService +{ + /// + /// Allows the service to control the host while running + /// + public interface HostControl + { + /// + /// Tells the Host that the service is still starting, which resets the + /// timeout. + /// + void RequestAdditionalTime(TimeSpan timeRemaining); + + /// + /// Stops the Host + /// + void Stop(); + + /// + /// Stops the Host, returning the specified exit code + /// + void Stop(ExitCode exitCode); + } +} diff --git a/ZeroLevel/Services/AsService/HostEnvironment.cs b/ZeroLevel/Services/AsService/HostEnvironment.cs new file mode 100644 index 0000000..6a3aa58 --- /dev/null +++ b/ZeroLevel/Services/AsService/HostEnvironment.cs @@ -0,0 +1,90 @@ +using System; + +namespace ZeroLevel.Services.AsService +{ + /// + /// Abstracts the environment in which the host in running (different OS versions, platforms, + /// bitness, etc.) + /// + public interface HostEnvironment + { + string CommandLine { get; } + + /// + /// Determines if the service is running as an administrator + /// + bool IsAdministrator { get; } + + /// + /// Determines if the process is running as a service + /// + bool IsRunningAsAService { get; } + + /// + /// Determines if the service is installed + /// + /// The name of the service as it is registered + /// True if the service is installed, otherwise false + bool IsServiceInstalled(string serviceName); + + /// + /// Determines if the service is stopped, to prevent a debug instance from being started + /// + /// + /// + bool IsServiceStopped(string serviceName); + + /// + /// Start the service using operating system controls + /// + /// The name of the service + /// Waits for the service to reach the running status in the specified time. + void StartService(string serviceName, TimeSpan startTimeOut); + + /// + /// Stop the service using operating system controls + /// + /// The name of the service + /// Waits for the service to reach the stopeed status in the specified time. + void StopService(string serviceName, TimeSpan stopTimeOut); + + /// + /// Install the service using the settings provided + /// + /// + /// + /// + /// + /// + void InstallService(InstallHostSettings settings, Action beforeInstall, Action afterInstall, Action beforeRollback, Action afterRollback); + + /// + /// Uninstall the service using the settings provided + /// + /// + /// + /// + void UninstallService(HostSettings settings, Action beforeUninstall, Action afterUninstall); + + /// + /// Restarts the service as an administrator which has permission to modify the service configuration + /// + /// True if the child process was executed, otherwise false + bool RunAsAdministrator(); + + /// + /// Create a service host appropriate for the host environment + /// + /// + /// + /// + Host CreateServiceHost(HostSettings settings, ServiceHandle serviceHandle); + + /// + /// Send a command to a service to make it do something + /// + /// The service name + /// The command value + void SendServiceCommand(string serviceName, int command); + } +} diff --git a/ZeroLevel/Services/AsService/HostSettings.cs b/ZeroLevel/Services/AsService/HostSettings.cs new file mode 100644 index 0000000..f07a49a --- /dev/null +++ b/ZeroLevel/Services/AsService/HostSettings.cs @@ -0,0 +1,96 @@ +using System; + +namespace ZeroLevel.Services.AsService +{ + public enum UnhandledExceptionPolicyCode + { + /// + /// If an UnhandledException occurs, AsService will log an error and + /// stop the service + /// + LogErrorAndStopService = 0, + /// + /// If an UnhandledException occurs, AsService will log an error and + /// continue without stopping the service + /// + LogErrorOnly = 1, + /// + /// If an UnhandledException occurs, AsService will take no action. + /// It is assumed that the application will handle the UnhandledException itself. + /// + TakeNoAction = 2 + } + /// + /// The settings that have been configured for the operating system service + /// + public interface HostSettings + { + /// + /// The name of the service + /// + string Name { get; } + + /// + /// The name of the service as it should be displayed in the service control manager + /// + string DisplayName { get; } + + /// + /// The description of the service that is displayed in the service control manager + /// + string Description { get; } + + /// + /// The service instance name that should be used when the service is registered + /// + string InstanceName { get; } + + /// + /// Returns the Windows service name, including the instance name, which is registered with the SCM Example: myservice$bob + /// + /// + string ServiceName { get; } + + /// + /// True if the service supports pause and continue + /// + bool CanPauseAndContinue { get; } + + /// + /// True if the service can handle the shutdown event + /// + bool CanShutdown { get; } + + /// + /// True if the service handles session change events + /// + bool CanSessionChanged { get; } + + /// + /// True if the service handles power change events + /// + bool CanHandlePowerEvent { get; } + + /// + /// The amount of time to wait for the service to start before timing out. Default is 10 seconds. + /// + TimeSpan StartTimeOut { get; } + + /// + /// The amount of time to wait for the service to stop before timing out. Default is 10 seconds. + /// + TimeSpan StopTimeOut { get; } + + /// + /// A callback to provide visibility into exceptions while AsService is performing its + /// own handling. + /// + Action ExceptionCallback { get; } + + /// + /// The policy that will be used when Topself detects an UnhandledException in the + /// application. The default policy is to log an error and to stop the service. + /// + UnhandledExceptionPolicyCode UnhandledExceptionPolicy { get; } + } +} diff --git a/ZeroLevel/Services/AsService/HostStartMode.cs b/ZeroLevel/Services/AsService/HostStartMode.cs new file mode 100644 index 0000000..9076734 --- /dev/null +++ b/ZeroLevel/Services/AsService/HostStartMode.cs @@ -0,0 +1,10 @@ +namespace ZeroLevel.Services.AsService +{ + public enum HostStartMode + { + Automatic = 0, + Manual = 1, + Disabled = 2, + AutomaticDelayed = 3, + } +} diff --git a/ZeroLevel/Services/AsService/Install/ComponentInstaller.cs b/ZeroLevel/Services/AsService/Install/ComponentInstaller.cs new file mode 100644 index 0000000..571e46e --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/ComponentInstaller.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace ZeroLevel.Services.AsService +{ + public abstract class ComponentInstaller + : Installer + { + /// When overridden in a derived class, copies all the properties that are required at install time from the specified component. + /// The component to copy from. + public abstract void CopyFromComponent(IComponent component); + + /// Determines if the specified installer installs the same object as this installer. + /// true if this installer and the installer specified by the parameter install the same object; otherwise, false. + /// The installer to compare. + public virtual bool IsEquivalentInstaller(ComponentInstaller otherInstaller) + { + return false; + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/InstallContext.cs b/ZeroLevel/Services/AsService/Install/InstallContext.cs new file mode 100644 index 0000000..0637eb2 --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/InstallContext.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Text; + +namespace ZeroLevel.Services.AsService +{ + public class InstallContext + { + private StringDictionary parameters; + + public StringDictionary Parameters => this.parameters; + + public InstallContext() : this(null, null) + { + } + + public InstallContext(string logFilePath, string[] commandLine) + { + this.parameters = InstallContext.ParseCommandLine(commandLine); + if (this.Parameters["logfile"] == null && logFilePath != null) + { + this.Parameters["logfile"] = logFilePath; + } + } + + public bool IsParameterTrue(string paramName) + { + string text = this.Parameters[paramName.ToLower(CultureInfo.InvariantCulture)]; + if (text == null) + { + return false; + } + + if (string.Compare(text, "true", StringComparison.OrdinalIgnoreCase) != 0 + && string.Compare(text, "yes", StringComparison.OrdinalIgnoreCase) != 0 + && string.Compare(text, "1", StringComparison.OrdinalIgnoreCase) != 0) + { + return "".Equals(text); + } + return true; + } + + public void LogMessage(string message) + { + try + { + this.LogMessageHelper(message); + } + catch (Exception) + { + try + { + this.Parameters["logfile"] = Path.Combine(Path.GetTempPath(), Path.GetFileName(this.Parameters["logfile"])); + this.LogMessageHelper(message); + } + catch (Exception) + { + this.Parameters["logfile"] = null; + } + } + if (!this.IsParameterTrue("LogToConsole") && this.Parameters["logtoconsole"] != null) + { + return; + } + Console.WriteLine(message); + } + + internal void LogMessageHelper(string message) + { + StreamWriter streamWriter = null; + try + { + if (!string.IsNullOrEmpty(this.Parameters["logfile"])) + { + streamWriter = new StreamWriter(this.Parameters["logfile"], true, Encoding.UTF8); + streamWriter.WriteLine(message); + } + } + finally + { + if (streamWriter != null) + { + streamWriter.Close(); + } + } + } + + protected static StringDictionary ParseCommandLine(string[] args) + { + StringDictionary stringDictionary = new StringDictionary(); + if (args == null) + { + return stringDictionary; + } + for (int i = 0; i < args.Length; i++) + { + if (args[i].StartsWith("/", StringComparison.Ordinal) || args[i].StartsWith("-", StringComparison.Ordinal)) + { + args[i] = args[i].Substring(1); + } + int num = args[i].IndexOf('='); + if (num < 0) + { + stringDictionary[args[i].ToLower(CultureInfo.InvariantCulture)] = ""; + } + else + { + stringDictionary[args[i].Substring(0, num).ToLower(CultureInfo.InvariantCulture)] = args[i].Substring(num + 1); + } + } + return stringDictionary; + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/InstallEventArgs.cs b/ZeroLevel/Services/AsService/Install/InstallEventArgs.cs new file mode 100644 index 0000000..d684d28 --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/InstallEventArgs.cs @@ -0,0 +1,20 @@ +using System.Collections; + +namespace ZeroLevel.Services.AsService +{ + public delegate void InstallEventHandler(object sender, InstallEventArgs e); + + public class InstallEventArgs + { + public IDictionary SavedSate { get; } + + public InstallEventArgs() + { + } + + public InstallEventArgs(IDictionary savedSate) + { + this.SavedSate = savedSate; + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/InstallException.cs b/ZeroLevel/Services/AsService/Install/InstallException.cs new file mode 100644 index 0000000..08f209d --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/InstallException.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.Serialization; + +namespace ZeroLevel.Services.AsService +{ + [Serializable] + internal class InstallException : SystemException + { + public InstallException() + { + base.HResult = -2146232057; + } + + public InstallException(string message) : base(message) + { + } + + public InstallException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InstallException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/InstallHostSettings.cs b/ZeroLevel/Services/AsService/Install/InstallHostSettings.cs new file mode 100644 index 0000000..6f1f174 --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/InstallHostSettings.cs @@ -0,0 +1,10 @@ +namespace ZeroLevel.Services.AsService +{ + public interface InstallHostSettings : + HostSettings + { + Credentials Credentials { get; set; } + string[] Dependencies { get; } + HostStartMode StartMode { get; } + } +} diff --git a/ZeroLevel/Services/AsService/Install/Installer.cs b/ZeroLevel/Services/AsService/Install/Installer.cs new file mode 100644 index 0000000..be38ca2 --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/Installer.cs @@ -0,0 +1,573 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace ZeroLevel.Services.AsService +{ + public class Installer : System.ComponentModel.Component + { + internal Installer parent; + private InstallerCollection installers; + + public InstallContext Context { get; set; } + public InstallerCollection Installers + { + get + { + if (this.installers == null) + { + this.installers = new InstallerCollection(this); + } + + return this.installers; + } + } + + [ResDescription("Desc_Installer_HelpText")] + public virtual string HelpText + { + get + { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < this.Installers.Count; i++) + { + string helpText = this.Installers[i].HelpText; + if (helpText.Length > 0) + { + stringBuilder.Append("\r\n"); + stringBuilder.Append(helpText); + } + } + return stringBuilder.ToString(); + } + } + + [TypeConverter(typeof(InstallerParentConverter))] + [ResDescription("Desc_Installer_Parent")] + public Installer Parent + { + get + { + return this.parent; + } + set + { + if (value == this) + { + throw new InvalidOperationException(Res.GetString("InstallBadParent")); + } + if (value != this.parent) + { + if (value != null && this.InstallerTreeContains(value)) + { + throw new InvalidOperationException(Res.GetString("InstallRecursiveParent")); + } + if (this.parent != null) + { + int num = this.parent.Installers.IndexOf(this); + if (num != -1) + { + this.parent.Installers.RemoveAt(num); + } + } + this.parent = value; + if (this.parent != null && !this.parent.Installers.Contains(this)) + { + this.parent.Installers.Add(this); + } + } + } + } + public virtual void Commit(IDictionary savedState) + { + if (savedState == null) + { + throw new ArgumentException(Res.GetString("InstallNullParameter", "savedState")); + } + if (savedState["_reserved_lastInstallerAttempted"] != null && savedState["_reserved_nestedSavedStates"] != null) + { + Exception ex = null; + try + { + this.OnCommitting(savedState); + } + catch (Exception ex2) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityWarning"), "OnCommitting", ex2); + this.Context.LogMessage(Res.GetString("InstallCommitException")); + ex = ex2; + } + int num = (int)savedState["_reserved_lastInstallerAttempted"]; + IDictionary[] array = (IDictionary[])savedState["_reserved_nestedSavedStates"]; + if (num + 1 == array.Length && num < this.Installers.Count) + { + for (int i = 0; i < this.Installers.Count; i++) + { + this.Installers[i].Context = this.Context; + } + for (int j = 0; j <= num; j++) + { + try + { + this.Installers[j].Commit(array[j]); + } + catch (Exception ex3) + { + if (!this.IsWrappedException(ex3)) + { + this.Context.LogMessage(Res.GetString("InstallLogCommitException", this.Installers[j].ToString())); + Installer.LogException(ex3, this.Context); + this.Context.LogMessage(Res.GetString("InstallCommitException")); + } + ex = ex3; + } + } + savedState["_reserved_nestedSavedStates"] = array; + savedState.Remove("_reserved_lastInstallerAttempted"); + try + { + this.OnCommitted(savedState); + } + catch (Exception ex4) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityWarning"), "OnCommitted", ex4); + this.Context.LogMessage(Res.GetString("InstallCommitException")); + ex = ex4; + } + if (ex == null) + { + return; + } + Exception ex5 = ex; + if (!this.IsWrappedException(ex)) + { + ex5 = new InstallException(Res.GetString("InstallCommitException"), ex); + ex5.Source = "WrappedExceptionSource"; + } + throw ex5; + } + throw new ArgumentException(Res.GetString("InstallDictionaryCorrupted", "savedState")); + } + throw new ArgumentException(Res.GetString("InstallDictionaryMissingValues", "savedState")); + } + + public virtual void Install(IDictionary stateSaver) + { + if (stateSaver == null) + { + throw new ArgumentNullException(nameof(stateSaver)); + } + try + { + this.OnBeforeInstall(stateSaver); + } + catch (Exception ex) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityError"), "OnBeforeInstall", ex); + throw new InvalidOperationException(Res.GetString("InstallEventException", "OnBeforeInstall", base.GetType().FullName), ex); + } + int num = -1; + List arrayList = new List(); + try + { + for (int i = 0; i < this.Installers.Count; i++) + { + this.Installers[i].Context = this.Context; + } + for (int j = 0; j < this.Installers.Count; j++) + { + Installer installer = this.Installers[j]; + IDictionary dictionary = new Hashtable(); + try + { + num = j; + installer.Install(dictionary); + } + finally + { + arrayList.Add(dictionary); + } + } + } + finally + { + stateSaver.Add("_reserved_lastInstallerAttempted", num); + stateSaver.Add("_reserved_nestedSavedStates", arrayList.ToArray()); + } + try + { + this.OnAfterInstall(stateSaver); + } + catch (Exception ex2) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityError"), "OnAfterInstall", ex2); + throw new InvalidOperationException(Res.GetString("InstallEventException", "OnAfterInstall", base.GetType().FullName), ex2); + } + } + + public virtual void Rollback(IDictionary savedState) + { + if (savedState == null) + { + throw new ArgumentException(Res.GetString("InstallNullParameter", "savedState")); + } + if (savedState["_reserved_lastInstallerAttempted"] != null && savedState["_reserved_nestedSavedStates"] != null) + { + Exception ex = null; + try + { + this.OnBeforeRollback(savedState); + } + catch (Exception ex2) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityWarning"), "OnBeforeRollback", ex2); + this.Context.LogMessage(Res.GetString("InstallRollbackException")); + ex = ex2; + } + int num = (int)savedState["_reserved_lastInstallerAttempted"]; + IDictionary[] array = (IDictionary[])savedState["_reserved_nestedSavedStates"]; + if (num + 1 == array.Length && num < this.Installers.Count) + { + for (int num2 = this.Installers.Count - 1; num2 >= 0; num2--) + { + this.Installers[num2].Context = this.Context; + } + for (int num3 = num; num3 >= 0; num3--) + { + try + { + this.Installers[num3].Rollback(array[num3]); + } + catch (Exception ex3) + { + if (!this.IsWrappedException(ex3)) + { + this.Context.LogMessage(Res.GetString("InstallLogRollbackException", this.Installers[num3].ToString())); + Installer.LogException(ex3, this.Context); + this.Context.LogMessage(Res.GetString("InstallRollbackException")); + } + ex = ex3; + } + } + try + { + this.OnAfterRollback(savedState); + } + catch (Exception ex4) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityWarning"), "OnAfterRollback", ex4); + this.Context.LogMessage(Res.GetString("InstallRollbackException")); + ex = ex4; + } + if (ex == null) + { + return; + } + Exception ex5 = ex; + if (!this.IsWrappedException(ex)) + { + ex5 = new InstallException(Res.GetString("InstallRollbackException"), ex); + ex5.Source = "WrappedExceptionSource"; + } + throw ex5; + } + throw new ArgumentException(Res.GetString("InstallDictionaryCorrupted", "savedState")); + } + throw new ArgumentException(Res.GetString("InstallDictionaryMissingValues", "savedState")); + } + + public virtual void Uninstall(IDictionary savedState) + { + Exception ex = null; + try + { + this.OnBeforeUninstall(savedState); + } + catch (Exception ex2) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityWarning"), "OnBeforeUninstall", ex2); + this.Context.LogMessage(Res.GetString("InstallUninstallException")); + ex = ex2; + } + IDictionary[] array; + if (savedState != null) + { + array = (IDictionary[])savedState["_reserved_nestedSavedStates"]; + if (array != null && array.Length == this.Installers.Count) + { + goto IL_0091; + } + throw new ArgumentException(Res.GetString("InstallDictionaryCorrupted", "savedState")); + } + array = new IDictionary[this.Installers.Count]; + goto IL_0091; + IL_0091: + for (int num = this.Installers.Count - 1; num >= 0; num--) + { + this.Installers[num].Context = this.Context; + } + for (int num2 = this.Installers.Count - 1; num2 >= 0; num2--) + { + try + { + this.Installers[num2].Uninstall(array[num2]); + } + catch (Exception ex3) + { + if (!this.IsWrappedException(ex3)) + { + this.Context.LogMessage(Res.GetString("InstallLogUninstallException", this.Installers[num2].ToString())); + Installer.LogException(ex3, this.Context); + this.Context.LogMessage(Res.GetString("InstallUninstallException")); + } + ex = ex3; + } + } + try + { + this.OnAfterUninstall(savedState); + } + catch (Exception ex4) + { + this.WriteEventHandlerError(Res.GetString("InstallSeverityWarning"), "OnAfterUninstall", ex4); + this.Context.LogMessage(Res.GetString("InstallUninstallException")); + ex = ex4; + } + if (ex == null) + { + return; + } + Exception ex5 = ex; + if (!this.IsWrappedException(ex)) + { + ex5 = new InstallException(Res.GetString("InstallUninstallException"), ex); + ex5.Source = "WrappedExceptionSource"; + } + throw ex5; + } + + #region Event Handlers + + private InstallEventHandler afterCommitHandler; + private InstallEventHandler afterInstallHandler; + private InstallEventHandler afterRollbackHandler; + private InstallEventHandler afterUninstallHandler; + private InstallEventHandler beforeCommitHandler; + private InstallEventHandler beforeInstallHandler; + private InstallEventHandler beforeRollbackHandler; + private InstallEventHandler beforeUninstallHandler; + + public event InstallEventHandler Committed + { + add + { + this.afterCommitHandler = (InstallEventHandler)Delegate.Combine(this.afterCommitHandler, value); + } + remove + { + this.afterCommitHandler = (InstallEventHandler)Delegate.Remove(this.afterCommitHandler, value); + } + } + + /// Occurs after the methods of all the installers in the property have run. + public event InstallEventHandler AfterInstall + { + add + { + this.afterInstallHandler = (InstallEventHandler)Delegate.Combine(this.afterInstallHandler, value); + } + remove + { + this.afterInstallHandler = (InstallEventHandler)Delegate.Remove(this.afterInstallHandler, value); + } + } + + /// Occurs after the installations of all the installers in the property are rolled back. + public event InstallEventHandler AfterRollback + { + add + { + this.afterRollbackHandler = (InstallEventHandler)Delegate.Combine(this.afterRollbackHandler, value); + } + remove + { + this.afterRollbackHandler = (InstallEventHandler)Delegate.Remove(this.afterRollbackHandler, value); + } + } + + /// Occurs after all the installers in the property perform their uninstallation operations. + public event InstallEventHandler AfterUninstall + { + add + { + this.afterUninstallHandler = (InstallEventHandler)Delegate.Combine(this.afterUninstallHandler, value); + } + remove + { + this.afterUninstallHandler = (InstallEventHandler)Delegate.Remove(this.afterUninstallHandler, value); + } + } + + /// Occurs before the installers in the property committ their installations. + public event InstallEventHandler Committing + { + add + { + this.beforeCommitHandler = (InstallEventHandler)Delegate.Combine(this.beforeCommitHandler, value); + } + remove + { + this.beforeCommitHandler = (InstallEventHandler)Delegate.Remove(this.beforeCommitHandler, value); + } + } + + /// Occurs before the method of each installer in the installer collection has run. + public event InstallEventHandler BeforeInstall + { + add + { + this.beforeInstallHandler = (InstallEventHandler)Delegate.Combine(this.beforeInstallHandler, value); + } + remove + { + this.beforeInstallHandler = (InstallEventHandler)Delegate.Remove(this.beforeInstallHandler, value); + } + } + + /// Occurs before the installers in the property are rolled back. + public event InstallEventHandler BeforeRollback + { + add + { + this.beforeRollbackHandler = (InstallEventHandler)Delegate.Combine(this.beforeRollbackHandler, value); + } + remove + { + this.beforeRollbackHandler = (InstallEventHandler)Delegate.Remove(this.beforeRollbackHandler, value); + } + } + + /// Occurs before the installers in the property perform their uninstall operations. + public event InstallEventHandler BeforeUninstall + { + add + { + this.beforeUninstallHandler = (InstallEventHandler)Delegate.Combine(this.beforeUninstallHandler, value); + } + remove + { + this.beforeUninstallHandler = (InstallEventHandler)Delegate.Remove(this.beforeUninstallHandler, value); + } + } + protected virtual void OnCommitted(IDictionary savedState) + { + this.afterCommitHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + + /// Raises the event. + /// An that contains the state of the computer after all the installers contained in the property have completed their installations. + protected virtual void OnAfterInstall(IDictionary savedState) + { + this.afterInstallHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + + /// Raises the event. + /// An that contains the state of the computer after the installers contained in the property are rolled back. + protected virtual void OnAfterRollback(IDictionary savedState) + { + this.afterRollbackHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + + /// Raises the event. + /// An that contains the state of the computer after all the installers contained in the property are uninstalled. + protected virtual void OnAfterUninstall(IDictionary savedState) + { + this.afterUninstallHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + + /// Raises the event. + /// An that contains the state of the computer before the installers in the property are committed. + protected virtual void OnCommitting(IDictionary savedState) + { + this.beforeCommitHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + + /// Raises the event. + /// An that contains the state of the computer before the installers in the property are installed. This object should be empty at this point. + protected virtual void OnBeforeInstall(IDictionary savedState) + { + this.beforeInstallHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + + /// Raises the event. + /// An that contains the state of the computer before the installers in the property are rolled back. + protected virtual void OnBeforeRollback(IDictionary savedState) + { + this.beforeRollbackHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + + /// Raises the event. + /// An that contains the state of the computer before the installers in the property uninstall their installations. + protected virtual void OnBeforeUninstall(IDictionary savedState) + { + this.beforeUninstallHandler?.Invoke(this, new InstallEventArgs(savedState)); + } + #endregion + + private void WriteEventHandlerError(string severity, string eventName, Exception e) + { + this.Context.LogMessage(Res.GetString("InstallLogError", severity, eventName, base.GetType().FullName)); + Installer.LogException(e, this.Context); + } + + internal static void LogException(Exception e, InstallContext context) + { + bool flag = true; + while (e != null) + { + if (flag) + { + context.LogMessage(e.GetType().FullName + ": " + e.Message); + flag = false; + } + else + { + context.LogMessage(Res.GetString("InstallLogInner", e.GetType().FullName, e.Message)); + } + if (context.IsParameterTrue("showcallstack")) + { + context.LogMessage(e.StackTrace); + } + e = e.InnerException; + } + } + + private bool IsWrappedException(Exception e) + { + if (e is InstallException && e.Source == "WrappedExceptionSource") + { + return e.TargetSite.ReflectedType == typeof(Installer); + } + return false; + } + + internal bool InstallerTreeContains(Installer target) + { + if (this.Installers.Contains(target)) + { + return true; + } + foreach (Installer installer in this.Installers) + { + if (installer.InstallerTreeContains(target)) + { + return true; + } + } + return false; + } + + } +} diff --git a/ZeroLevel/Services/AsService/Install/InstallerCollection.cs b/ZeroLevel/Services/AsService/Install/InstallerCollection.cs new file mode 100644 index 0000000..9c4fa4c --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/InstallerCollection.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; + +namespace ZeroLevel.Services.AsService +{ + public class InstallerCollection + : System.Collections.ObjectModel.Collection + { + private readonly Installer owner; + + internal InstallerCollection(Installer owner) + { + this.owner = owner; + } + + public void AddRange(IEnumerable value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + foreach (var item in value) + { + this.Add(item); + } + } + + protected override void InsertItem(int index, Installer item) + { + if (item == this.owner) + { + throw new ArgumentException(Res.GetString("CantAddSelf")); + } + + item.parent = this.owner; + base.InsertItem(index, item); + } + + protected override void RemoveItem(int index) + { + this[index].parent = null; + base.RemoveItem(index); + } + + protected override void SetItem(int index, Installer item) + { + if (item == this.owner) + { + throw new ArgumentException(Res.GetString("CantAddSelf")); + } + + this[index].parent = null; + item.parent = owner; + base.SetItem(index, item); + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/InstallerParentConverter.cs b/ZeroLevel/Services/AsService/Install/InstallerParentConverter.cs new file mode 100644 index 0000000..e9abd4f --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/InstallerParentConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel; + +namespace ZeroLevel.Services.AsService +{ + internal class InstallerParentConverter + : ReferenceConverter + { + public InstallerParentConverter(Type type) + : base(type) + { + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + StandardValuesCollection standardValues = base.GetStandardValues(context); + object instance = context.Instance; + int i = 0; + int num = 0; + object[] array = new object[standardValues.Count - 1]; + for (; i < standardValues.Count; i++) + { + if (standardValues[i] != instance) + { + array[num] = standardValues[i]; + num++; + } + } + return new StandardValuesCollection(array); + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/ServiceInstaller.cs b/ZeroLevel/Services/AsService/Install/ServiceInstaller.cs new file mode 100644 index 0000000..9751a41 --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/ServiceInstaller.cs @@ -0,0 +1,493 @@ +using System; +using System.Text; +using System.ServiceProcess; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Collections; +using System.Threading; + +namespace ZeroLevel.Services.AsService +{ + public class ServiceInstaller + : ComponentInstaller + { + + private const string NetworkServiceName = "NT AUTHORITY\\NetworkService"; + + private const string LocalServiceName = "NT AUTHORITY\\LocalService"; + + //private EventLogInstaller eventLogInstaller; + + private string serviceName = ""; + + private string displayName = ""; + + private string description = ""; + + private string[] servicesDependedOn = new string[0]; + + private ServiceStartMode startType = ServiceStartMode.Manual; + + private bool delayedStartMode; + + private static bool environmentChecked; + + private static bool isWin9x; + + /// Indicates the friendly name that identifies the service to the user. + /// The name associated with the service, used frequently for interactive tools. + [DefaultValue("")] + [ServiceProcessDescription("ServiceInstallerDisplayName")] + public string DisplayName + { + get + { + return this.displayName; + } + set + { + if (value == null) + { + value = ""; + } + this.displayName = value; + } + } + + /// Gets or sets the description for the service. + /// The description of the service. The default is an empty string (""). + [DefaultValue("")] + [ComVisible(false)] + [ServiceProcessDescription("ServiceInstallerDescription")] + public string Description + { + get + { + return this.description; + } + set + { + if (value == null) + { + value = ""; + } + this.description = value; + } + } + + /// Indicates the services that must be running for this service to run. + /// An array of services that must be running before the service associated with this installer can run. + [ServiceProcessDescription("ServiceInstallerServicesDependedOn")] + public string[] ServicesDependedOn + { + get + { + return this.servicesDependedOn; + } + set + { + if (value == null) + { + value = new string[0]; + } + this.servicesDependedOn = value; + } + } + + /// Indicates the name used by the system to identify this service. This property must be identical to the of the service you want to install. + /// The name of the service to be installed. This value must be set before the install utility attempts to install the service. + /// The property is invalid. + [DefaultValue("")] + [TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [ServiceProcessDescription("ServiceInstallerServiceName")] + public string ServiceName + { + get + { + return this.serviceName; + } + set + { + if (value == null) + { + value = ""; + } + if (!ServiceControllerExtensions.ValidServiceName(value)) + { + throw new ArgumentException(Res.GetString("ServiceName", value, 80.ToString(CultureInfo.CurrentCulture))); + } + this.serviceName = value; + //this.eventLogInstaller.Source = value; + } + } + + /// Indicates how and when this service is started. + /// A that represents the way the service is started. The default is Manual, which specifies that the service will not automatically start after reboot. + /// The start mode is not a value of the enumeration. + /// + /// + /// + [DefaultValue(ServiceStartMode.Manual)] + [ServiceProcessDescription("ServiceInstallerStartType")] + public ServiceStartMode StartType + { + get + { + return this.startType; + } + set + { + if (!Enum.IsDefined(typeof(ServiceStartMode), value)) + { + throw new InvalidEnumArgumentException("value", (int)value, typeof(ServiceStartMode)); + } + if (value != 0 && value != ServiceStartMode.System) + { + this.startType = value; + return; + } + throw new ArgumentException(Res.GetString("ServiceStartType", value)); + } + } + + /// Gets or sets a value that indicates whether the service should be delayed from starting until other automatically started services are running. + /// true to delay automatic start of the service; otherwise, false. The default is false. + [DefaultValue(false)] + [ServiceProcessDescription("ServiceInstallerDelayedAutoStart")] + public bool DelayedAutoStart + { + get + { + return this.delayedStartMode; + } + set + { + this.delayedStartMode = value; + } + } + + /// Initializes a new instance of the class. + public ServiceInstaller() + { + //this.eventLogInstaller = new EventLogInstaller(); + //this.eventLogInstaller.Log = "Application"; + //this.eventLogInstaller.Source = ""; + //this.eventLogInstaller.UninstallAction = UninstallAction.Remove; + //base.Installers.Add(this.eventLogInstaller); + } + + internal static void CheckEnvironment() + { + if (ServiceInstaller.environmentChecked) + { + if (!ServiceInstaller.isWin9x) + { + return; + } + throw new PlatformNotSupportedException(Res.GetString("CantControlOnWin9x")); + } + ServiceInstaller.isWin9x = (Environment.OSVersion.Platform != PlatformID.Win32NT); + ServiceInstaller.environmentChecked = true; + if (!ServiceInstaller.isWin9x) + { + return; + } + throw new PlatformNotSupportedException(Res.GetString("CantInstallOnWin9x")); + } + + /// Copies properties from an instance of to this installer. + /// The from which to copy. + /// The component you are associating with this installer does not inherit from . + public override void CopyFromComponent(IComponent component) + { + if (!(component is ServiceBase)) + { + throw new ArgumentException(Res.GetString("NotAService")); + } + ServiceBase serviceBase = (ServiceBase)component; + this.ServiceName = serviceBase.ServiceName; + } + + /// Installs the service by writing service application information to the registry. This method is meant to be used by installation tools, which process the appropriate methods automatically. + /// An that contains the context information associated with the installation. + /// The installation does not contain a for the executable.-or- The file name for the assembly is null or an empty string.-or- The service name is invalid.-or- The Service Control Manager could not be opened. + /// The display name for the service is more than 255 characters in length. + /// The system could not generate a handle to the service. -or-A service with that name is already installed. + /// + /// + /// + /// + /// + public override void Install(IDictionary stateSaver) + { + base.Context.LogMessage(Res.GetString("InstallingService", this.ServiceName)); + try + { + ServiceInstaller.CheckEnvironment(); + string servicesStartName = null; + string password = null; + ServiceProcessInstaller serviceProcessInstaller = null; + if (base.Parent is ServiceProcessInstaller) + { + serviceProcessInstaller = (ServiceProcessInstaller)base.Parent; + } + else + { + int num = 0; + while (num < base.Parent.Installers.Count) + { + if (!(base.Parent.Installers[num] is ServiceProcessInstaller)) + { + num++; + continue; + } + serviceProcessInstaller = (ServiceProcessInstaller)base.Parent.Installers[num]; + break; + } + } + if (serviceProcessInstaller == null) + { + throw new InvalidOperationException(Res.GetString("NoInstaller")); + } + switch (serviceProcessInstaller.Account) + { + case ServiceAccount.LocalService: + servicesStartName = "NT AUTHORITY\\LocalService"; + break; + case ServiceAccount.NetworkService: + servicesStartName = "NT AUTHORITY\\NetworkService"; + break; + case ServiceAccount.User: + servicesStartName = serviceProcessInstaller.Username; + password = serviceProcessInstaller.Password; + break; + } + string text = base.Context.Parameters["assemblypath"]; + if (string.IsNullOrEmpty(text)) + { + throw new InvalidOperationException(Res.GetString("FileName")); + } + if (text.IndexOf('"') == -1) + { + text = "\"" + text + "\""; + } + if (!ServiceInstaller.ValidateServiceName(this.ServiceName)) + { + throw new InvalidOperationException(Res.GetString("ServiceName", this.ServiceName, 80.ToString(CultureInfo.CurrentCulture))); + } + if (this.DisplayName.Length > 255) + { + throw new ArgumentException(Res.GetString("DisplayNameTooLong", this.DisplayName)); + } + string dependencies = null; + if (this.ServicesDependedOn.Length != 0) + { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < this.ServicesDependedOn.Length; i++) + { + string text2 = this.ServicesDependedOn[i]; + try + { + text2 = new ServiceController(text2, ".").ServiceName; + } + catch + { + } + stringBuilder.Append(text2); + stringBuilder.Append('\0'); + } + stringBuilder.Append('\0'); + dependencies = stringBuilder.ToString(); + } + IntPtr intPtr = SafeNativeMethods.OpenSCManager(null, null, 983103); + IntPtr intPtr2 = IntPtr.Zero; + if (intPtr == IntPtr.Zero) + { + throw new InvalidOperationException(Res.GetString("OpenSC", "."), new Win32Exception()); + } + int serviceType = 16; + int num2 = 0; + for (int j = 0; j < base.Parent.Installers.Count; j++) + { + if (base.Parent.Installers[j] is ServiceInstaller) + { + num2++; + if (num2 > 1) + { + break; + } + } + } + if (num2 > 1) + { + serviceType = 32; + } + try + { + intPtr2 = NativeMethods.CreateService(intPtr, this.ServiceName, this.DisplayName, 983551, serviceType, (int)this.StartType, 1, text, null, IntPtr.Zero, dependencies, servicesStartName, password); + if (intPtr2 == IntPtr.Zero) + { + throw new Win32Exception(); + } + if (this.Description.Length != 0) + { + NativeMethods.SERVICE_DESCRIPTION sERVICE_DESCRIPTION = default(NativeMethods.SERVICE_DESCRIPTION); + sERVICE_DESCRIPTION.description = Marshal.StringToHGlobalUni(this.Description); + bool num3 = NativeMethods.ChangeServiceConfig2(intPtr2, 1u, ref sERVICE_DESCRIPTION); + Marshal.FreeHGlobal(sERVICE_DESCRIPTION.description); + if (!num3) + { + throw new Win32Exception(); + } + } + if (Environment.OSVersion.Version.Major > 5 && this.StartType == ServiceStartMode.Automatic) + { + NativeMethods.SERVICE_DELAYED_AUTOSTART_INFO sERVICE_DELAYED_AUTOSTART_INFO = default(NativeMethods.SERVICE_DELAYED_AUTOSTART_INFO); + sERVICE_DELAYED_AUTOSTART_INFO.fDelayedAutostart = this.DelayedAutoStart; + if (!NativeMethods.ChangeServiceConfig2(intPtr2, 3u, ref sERVICE_DELAYED_AUTOSTART_INFO)) + { + throw new Win32Exception(); + } + } + stateSaver["installed"] = true; + } + finally + { + if (intPtr2 != IntPtr.Zero) + { + SafeNativeMethods.CloseServiceHandle(intPtr2); + } + SafeNativeMethods.CloseServiceHandle(intPtr); + } + base.Context.LogMessage(Res.GetString("InstallOK", this.ServiceName)); + } + finally + { + base.Install(stateSaver); + } + } + + /// Indicates whether two installers would install the same service. + /// true if calling on both of these installers would result in installing the same service; otherwise, false. + /// A to which you are comparing the current installer. + public override bool IsEquivalentInstaller(ComponentInstaller otherInstaller) + { + ServiceInstaller serviceInstaller = otherInstaller as ServiceInstaller; + if (serviceInstaller == null) + { + return false; + } + return serviceInstaller.ServiceName == this.ServiceName; + } + + private void RemoveService() + { + base.Context.LogMessage(Res.GetString("ServiceRemoving", this.ServiceName)); + IntPtr intPtr = SafeNativeMethods.OpenSCManager(null, null, 983103); + if (intPtr == IntPtr.Zero) + { + throw new Win32Exception(); + } + IntPtr intPtr2 = IntPtr.Zero; + try + { + intPtr2 = NativeMethods.OpenService(intPtr, this.ServiceName, 65536); + if (intPtr2 == IntPtr.Zero) + { + throw new Win32Exception(); + } + NativeMethods.DeleteService(intPtr2); + } + finally + { + if (intPtr2 != IntPtr.Zero) + { + SafeNativeMethods.CloseServiceHandle(intPtr2); + } + SafeNativeMethods.CloseServiceHandle(intPtr); + } + base.Context.LogMessage(Res.GetString("ServiceRemoved", this.ServiceName)); + try + { + using (ServiceController serviceController = new ServiceController(this.ServiceName)) + { + if (serviceController.Status != ServiceControllerStatus.Stopped) + { + base.Context.LogMessage(Res.GetString("TryToStop", this.ServiceName)); + serviceController.Stop(); + int num = 10; + serviceController.Refresh(); + while (serviceController.Status != ServiceControllerStatus.Stopped && num > 0) + { + Thread.Sleep(1000); + serviceController.Refresh(); + num--; + } + } + } + } + catch + { + } + Thread.Sleep(5000); + } + + /// Rolls back service application information written to the registry by the installation procedure. This method is meant to be used by installation tools, which process the appropriate methods automatically. + /// An that contains the context information associated with the installation. + /// + /// + /// + /// + /// + public override void Rollback(IDictionary savedState) + { + base.Rollback(savedState); + object obj = savedState["installed"]; + if (obj != null && (bool)obj) + { + this.RemoveService(); + } + } + + private bool ShouldSerializeServicesDependedOn() + { + if (this.servicesDependedOn != null && this.servicesDependedOn.Length != 0) + { + return true; + } + return false; + } + + /// Uninstalls the service by removing information about it from the registry. + /// An that contains the context information associated with the installation. + /// The Service Control Manager could not be opened.-or- The system could not get a handle to the service. + /// + /// + /// + /// + /// + public override void Uninstall(IDictionary savedState) + { + base.Uninstall(savedState); + this.RemoveService(); + } + + private static bool ValidateServiceName(string name) + { + if (name != null && name.Length != 0 && name.Length <= 80) + { + char[] array = name.ToCharArray(); + for (int i = 0; i < array.Length; i++) + { + if (array[i] < ' ' || array[i] == '/' || array[i] == '\\') + { + return false; + } + } + return true; + } + return false; + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/ServiceProcessInstaller.cs b/ZeroLevel/Services/AsService/Install/ServiceProcessInstaller.cs new file mode 100644 index 0000000..ac069ac --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/ServiceProcessInstaller.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.ServiceProcess; +using System.Text; + +namespace ZeroLevel.Services.AsService +{ + public class ServiceProcessInstaller + : ComponentInstaller + { + private ServiceAccount serviceAccount = ServiceAccount.User; + + private bool haveLoginInfo; + + private string password; + + private string username; + + private static bool helpPrinted; + + /// Gets help text displayed for service installation options. + /// Help text that provides a description of the steps for setting the user name and password in order to run the service under a particular account. + /// + /// + /// + public override string HelpText + { + get + { + if (ServiceProcessInstaller.helpPrinted) + { + return base.HelpText; + } + ServiceProcessInstaller.helpPrinted = true; + return Res.GetString("HelpText") + "\r\n" + base.HelpText; + } + } + + /// Gets or sets the password associated with the user account under which the service application runs. + /// The password associated with the account under which the service should run. The default is an empty string (""). The property is not public, and is never serialized. + /// + /// + /// + /// + /// + /// + /// + [Browsable(false)] + public string Password + { + get + { + if (!this.haveLoginInfo) + { + this.GetLoginInfo(); + } + return this.password; + } + set + { + this.haveLoginInfo = false; + this.password = value; + } + } + + /// Gets or sets the type of account under which to run this service application. + /// A that defines the type of account under which the system runs this service. The default is User. + /// + /// + /// + /// + /// + /// + /// + [DefaultValue(ServiceAccount.User)] + [ServiceProcessDescription("ServiceProcessInstallerAccount")] + public ServiceAccount Account + { + get + { + if (!this.haveLoginInfo) + { + this.GetLoginInfo(); + } + return this.serviceAccount; + } + set + { + this.haveLoginInfo = false; + this.serviceAccount = value; + } + } + + /// Gets or sets the user account under which the service application will run. + /// The account under which the service should run. The default is an empty string (""). + /// + /// + /// + /// + /// + /// + /// + [TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [Browsable(false)] + public string Username + { + get + { + if (!this.haveLoginInfo) + { + this.GetLoginInfo(); + } + return this.username; + } + set + { + this.haveLoginInfo = false; + this.username = value; + } + } + + private static bool AccountHasRight(IntPtr policyHandle, byte[] accountSid, string rightName) + { + IntPtr intPtr = (IntPtr)0; + int num = 0; + int num2 = NativeMethods.LsaEnumerateAccountRights(policyHandle, accountSid, out intPtr, out num); + switch (num2) + { + case -1073741772: + return false; + default: + throw new Win32Exception(SafeNativeMethods.LsaNtStatusToWinError(num2)); + case 0: + { + bool result = false; + try + { + IntPtr intPtr2 = intPtr; + for (int i = 0; i < num; i++) + { + NativeMethods.LSA_UNICODE_STRING_withPointer lSA_UNICODE_STRING_withPointer = new NativeMethods.LSA_UNICODE_STRING_withPointer(); + Marshal.PtrToStructure(intPtr2, (object)lSA_UNICODE_STRING_withPointer); + char[] array = new char[lSA_UNICODE_STRING_withPointer.length / 2]; + Marshal.Copy(lSA_UNICODE_STRING_withPointer.pwstr, array, 0, array.Length); + if (string.Compare(new string(array, 0, array.Length), rightName, StringComparison.Ordinal) == 0) + { + return true; + } + intPtr2 = (IntPtr)((long)intPtr2 + Marshal.SizeOf(typeof(NativeMethods.LSA_UNICODE_STRING))); + } + return result; + } + finally + { + SafeNativeMethods.LsaFreeMemory(intPtr); + } + } + } + } + + /// Implements the base class method with no class-specific behavior. + /// The that represents the service process. + public override void CopyFromComponent(IComponent comp) + { + } + + private byte[] GetAccountSid(string accountName) + { + byte[] array = new byte[256]; + int[] array2 = new int[1] + { + array.Length + }; + char[] array3 = new char[1024]; + int[] domNameLen = new int[1] + { + array3.Length + }; + int[] sidNameUse = new int[1]; + if (accountName.Substring(0, 2) == ".\\") + { + StringBuilder stringBuilder = new StringBuilder(32); + int num = 32; + if (!NativeMethods.GetComputerName(stringBuilder, ref num)) + { + throw new Win32Exception(); + } + accountName = stringBuilder + accountName.Substring(1); + } + if (!NativeMethods.LookupAccountName(null, accountName, array, array2, array3, domNameLen, sidNameUse)) + { + throw new Win32Exception(); + } + byte[] array4 = new byte[array2[0]]; + Array.Copy(array, 0, array4, 0, array2[0]); + return array4; + } + + private void GetLoginInfo() + { + if (base.Context != null && !base.DesignMode && !this.haveLoginInfo) + { + this.haveLoginInfo = true; + if (this.serviceAccount != ServiceAccount.User) + { + return; + } + if (base.Context.Parameters.ContainsKey("username")) + { + this.username = base.Context.Parameters["username"]; + } + if (base.Context.Parameters.ContainsKey("password")) + { + this.password = base.Context.Parameters["password"]; + } + if (this.username != null && this.username.Length != 0 && this.password != null) + { + return; + } + if (!base.Context.Parameters.ContainsKey("unattended")) + { + throw new PlatformNotSupportedException(); + } + throw new InvalidOperationException(Res.GetString("UnattendedCannotPrompt", base.Context.Parameters["assemblypath"])); + } + } + + private static void GrantAccountRight(IntPtr policyHandle, byte[] accountSid, string rightName) + { + NativeMethods.LSA_UNICODE_STRING lSA_UNICODE_STRING = new NativeMethods.LSA_UNICODE_STRING(); + lSA_UNICODE_STRING.buffer = rightName; + NativeMethods.LSA_UNICODE_STRING lSA_UNICODE_STRING2 = lSA_UNICODE_STRING; + lSA_UNICODE_STRING2.length = (short)(lSA_UNICODE_STRING2.buffer.Length * 2); + NativeMethods.LSA_UNICODE_STRING lSA_UNICODE_STRING3 = lSA_UNICODE_STRING; + lSA_UNICODE_STRING3.maximumLength = lSA_UNICODE_STRING3.length; + int num = NativeMethods.LsaAddAccountRights(policyHandle, accountSid, lSA_UNICODE_STRING, 1); + if (num == 0) + { + return; + } + throw new Win32Exception(SafeNativeMethods.LsaNtStatusToWinError(num)); + } + + /// Writes service application information to the registry. This method is meant to be used by installation tools, which call the appropriate methods automatically. + /// An that contains the context information associated with the installation. + /// The is null. + /// + /// + /// + /// + public override void Install(IDictionary stateSaver) + { + try + { + ServiceInstaller.CheckEnvironment(); + try + { + if (!this.haveLoginInfo) + { + try + { + this.GetLoginInfo(); + } + catch + { + stateSaver["hadServiceLogonRight"] = true; + throw; + } + } + } + finally + { + stateSaver["Account"] = this.Account; + if (this.Account == ServiceAccount.User) + { + stateSaver["Username"] = this.Username; + } + } + if (this.Account == ServiceAccount.User) + { + IntPtr intPtr = this.OpenSecurityPolicy(); + bool flag = true; + try + { + byte[] accountSid = this.GetAccountSid(this.Username); + flag = ServiceProcessInstaller.AccountHasRight(intPtr, accountSid, "SeServiceLogonRight"); + if (!flag) + { + ServiceProcessInstaller.GrantAccountRight(intPtr, accountSid, "SeServiceLogonRight"); + } + } + finally + { + stateSaver["hadServiceLogonRight"] = flag; + SafeNativeMethods.LsaClose(intPtr); + } + } + } + finally + { + base.Install(stateSaver); + } + } + + private IntPtr OpenSecurityPolicy() + { + GCHandle gCHandle = GCHandle.Alloc(new NativeMethods.LSA_OBJECT_ATTRIBUTES(), GCHandleType.Pinned); + try + { + int num = 0; + IntPtr pointerObjectAttributes = gCHandle.AddrOfPinnedObject(); + IntPtr result = default(IntPtr); + num = NativeMethods.LsaOpenPolicy((NativeMethods.LSA_UNICODE_STRING)null, pointerObjectAttributes, 2064, out result); + if (num != 0) + { + throw new Win32Exception(SafeNativeMethods.LsaNtStatusToWinError(num)); + } + return result; + } + finally + { + gCHandle.Free(); + } + } + + private static void RemoveAccountRight(IntPtr policyHandle, byte[] accountSid, string rightName) + { + NativeMethods.LSA_UNICODE_STRING lSA_UNICODE_STRING = new NativeMethods.LSA_UNICODE_STRING(); + lSA_UNICODE_STRING.buffer = rightName; + NativeMethods.LSA_UNICODE_STRING lSA_UNICODE_STRING2 = lSA_UNICODE_STRING; + lSA_UNICODE_STRING2.length = (short)(lSA_UNICODE_STRING2.buffer.Length * 2); + NativeMethods.LSA_UNICODE_STRING lSA_UNICODE_STRING3 = lSA_UNICODE_STRING; + lSA_UNICODE_STRING3.maximumLength = lSA_UNICODE_STRING3.length; + int num = NativeMethods.LsaRemoveAccountRights(policyHandle, accountSid, false, lSA_UNICODE_STRING, 1); + if (num == 0) + { + return; + } + throw new Win32Exception(SafeNativeMethods.LsaNtStatusToWinError(num)); + } + + /// Rolls back service application information written to the registry by the installation procedure. This method is meant to be used by installation tools, which process the appropriate methods automatically. + /// An that contains the context information associated with the installation. + /// The is null.-or- The is corrupted or non-existent. + /// + /// + /// + public override void Rollback(IDictionary savedState) + { + try + { + if ((ServiceAccount)savedState["Account"] == ServiceAccount.User && !(bool)savedState["hadServiceLogonRight"]) + { + string accountName = (string)savedState["Username"]; + IntPtr intPtr = this.OpenSecurityPolicy(); + try + { + byte[] accountSid = this.GetAccountSid(accountName); + ServiceProcessInstaller.RemoveAccountRight(intPtr, accountSid, "SeServiceLogonRight"); + } + finally + { + SafeNativeMethods.LsaClose(intPtr); + } + } + } + finally + { + base.Rollback(savedState); + } + } + } +} diff --git a/ZeroLevel/Services/AsService/Install/TransactedInstaller.cs b/ZeroLevel/Services/AsService/Install/TransactedInstaller.cs new file mode 100644 index 0000000..4fffa8a --- /dev/null +++ b/ZeroLevel/Services/AsService/Install/TransactedInstaller.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections; + +namespace ZeroLevel.Services.AsService +{ + public class TransactedInstaller + : Installer + { + public override void Install(IDictionary savedState) + { + if (base.Context == null) + { + base.Context = new InstallContext(); + } + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoTransacted")); + try + { + bool flag = true; + try + { + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoBeginInstall")); + base.Install(savedState); + } + catch (Exception ex) + { + flag = false; + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoException")); + Installer.LogException(ex, base.Context); + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoBeginRollback")); + try + { + this.Rollback(savedState); + } + catch (Exception) + { + } + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoRollbackDone")); + throw new InvalidOperationException(Res.GetString("InstallRollback"), ex); + } + if (flag) + { + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoBeginCommit")); + try + { + this.Commit(savedState); + } + finally + { + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoCommitDone")); + } + } + } + finally + { + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoTransactedDone")); + } + } + + public override void Uninstall(IDictionary savedState) + { + if (base.Context == null) + { + base.Context = new InstallContext(); + } + base.Context.LogMessage(Environment.NewLine + Environment.NewLine + Res.GetString("InstallInfoBeginUninstall")); + try + { + base.Uninstall(savedState); + } + finally + { + base.Context.LogMessage(Environment.NewLine + Res.GetString("InstallInfoUninstallDone")); + } + } + } +} diff --git a/ZeroLevel/Services/AsService/PowerEventArguments.cs b/ZeroLevel/Services/AsService/PowerEventArguments.cs new file mode 100644 index 0000000..7359526 --- /dev/null +++ b/ZeroLevel/Services/AsService/PowerEventArguments.cs @@ -0,0 +1,53 @@ +namespace ZeroLevel.Services.AsService +{ + public enum PowerEventCode + { + /// + /// The system has requested permission to suspend the computer. An application that grants permission should carry out preparations for the suspension before returning. + /// Not supported by + /// + QuerySuspend = 0, + /// + /// The system was denied permission to suspend the computer. This status is broadcast if any application or driver denied a previous status. + /// Not supported by + /// + QuerySuspendFailed = 2, + /// + /// The computer is about to enter a suspended state. This event is typically broadcast when all applications and installable drivers have returned true to a previous QuerySuspend state. + /// + Suspend = 4, + /// + /// The system has resumed operation after a critical suspension caused by a failing battery. + /// Not supported by + /// + ResumeCritical = 6, + /// + /// The system has resumed operation after being suspended. + /// Not supported by + /// + ResumeSuspend = 7, + /// + /// Battery power is low. + /// Not supported by + /// + BatteryLow = 9, + /// + /// A change in the power status of the computer is detected, such as a switch from battery power to A/C. The system also broadcasts this event when remaining battery power slips below the threshold specified by the user or if the battery power changes by a specified percentage. + /// + PowerStatusChange = 10, + /// + /// An Advanced Power Management (APM) BIOS signaled an APM OEM event. + /// Not supported by + /// + OemEvent = 11, + /// + /// The computer has woken up automatically to handle an event. + /// + ResumeAutomatic = 18 + } + + public interface PowerEventArguments + { + PowerEventCode EventCode { get; } + } +} diff --git a/ZeroLevel/Services/AsService/Res.cs b/ZeroLevel/Services/AsService/Res.cs new file mode 100644 index 0000000..b42966f --- /dev/null +++ b/ZeroLevel/Services/AsService/Res.cs @@ -0,0 +1,287 @@ +using System.Globalization; +using System.Resources; +using System.Threading; + +namespace ZeroLevel.Services.AsService +{ + internal sealed class Res + { + internal const string RTL = "RTL"; + + internal const string FileName = "FileName"; + + internal const string ServiceStartedIncorrectly = "ServiceStartedIncorrectly"; + + internal const string CallbackHandler = "CallbackHandler"; + + internal const string OpenService = "OpenService"; + + internal const string StartService = "StartService"; + + internal const string StopService = "StopService"; + + internal const string PauseService = "PauseService"; + + internal const string ResumeService = "ResumeService"; + + internal const string ControlService = "ControlService"; + + internal const string ServiceName = "ServiceName"; + + internal const string ServiceStartType = "ServiceStartType"; + + internal const string ServiceDependency = "ServiceDependency"; + + internal const string InstallService = "InstallService"; + + internal const string InstallError = "InstallError"; + + internal const string UserName = "UserName"; + + internal const string UserPassword = "UserPassword"; + + internal const string ButtonOK = "ButtonOK"; + + internal const string ServiceUsage = "ServiceUsage"; + + internal const string ServiceNameTooLongForNt4 = "ServiceNameTooLongForNt4"; + + internal const string DisplayNameTooLong = "DisplayNameTooLong"; + + internal const string NoService = "NoService"; + + internal const string NoDisplayName = "NoDisplayName"; + + internal const string OpenSC = "OpenSC"; + + internal const string Timeout = "Timeout"; + + internal const string CannotChangeProperties = "CannotChangeProperties"; + + internal const string CannotChangeName = "CannotChangeName"; + + internal const string NoServices = "NoServices"; + + internal const string NoMachineName = "NoMachineName"; + + internal const string BadMachineName = "BadMachineName"; + + internal const string NoGivenName = "NoGivenName"; + + internal const string CannotStart = "CannotStart"; + + internal const string NotAService = "NotAService"; + + internal const string NoInstaller = "NoInstaller"; + + internal const string UserCanceledInstall = "UserCanceledInstall"; + + internal const string UnattendedCannotPrompt = "UnattendedCannotPrompt"; + + internal const string InvalidParameter = "InvalidParameter"; + + internal const string FailedToUnloadAppDomain = "FailedToUnloadAppDomain"; + + internal const string NotInPendingState = "NotInPendingState"; + + internal const string ArgsCantBeNull = "ArgsCantBeNull"; + + internal const string StartSuccessful = "StartSuccessful"; + + internal const string StopSuccessful = "StopSuccessful"; + + internal const string PauseSuccessful = "PauseSuccessful"; + + internal const string ContinueSuccessful = "ContinueSuccessful"; + + internal const string InstallSuccessful = "InstallSuccessful"; + + internal const string UninstallSuccessful = "UninstallSuccessful"; + + internal const string CommandSuccessful = "CommandSuccessful"; + + internal const string StartFailed = "StartFailed"; + + internal const string StopFailed = "StopFailed"; + + internal const string PauseFailed = "PauseFailed"; + + internal const string ContinueFailed = "ContinueFailed"; + + internal const string SessionChangeFailed = "SessionChangeFailed"; + + internal const string InstallFailed = "InstallFailed"; + + internal const string UninstallFailed = "UninstallFailed"; + + internal const string CommandFailed = "CommandFailed"; + + internal const string ErrorNumber = "ErrorNumber"; + + internal const string ShutdownOK = "ShutdownOK"; + + internal const string ShutdownFailed = "ShutdownFailed"; + + internal const string PowerEventOK = "PowerEventOK"; + + internal const string PowerEventFailed = "PowerEventFailed"; + + internal const string InstallOK = "InstallOK"; + + internal const string TryToStop = "TryToStop"; + + internal const string ServiceRemoving = "ServiceRemoving"; + + internal const string ServiceRemoved = "ServiceRemoved"; + + internal const string HelpText = "HelpText"; + + internal const string CantStartFromCommandLine = "CantStartFromCommandLine"; + + internal const string CantStartFromCommandLineTitle = "CantStartFromCommandLineTitle"; + + internal const string CantRunOnWin9x = "CantRunOnWin9x"; + + internal const string CantRunOnWin9xTitle = "CantRunOnWin9xTitle"; + + internal const string CantControlOnWin9x = "CantControlOnWin9x"; + + internal const string CantInstallOnWin9x = "CantInstallOnWin9x"; + + internal const string InstallingService = "InstallingService"; + + internal const string StartingService = "StartingService"; + + internal const string SBAutoLog = "SBAutoLog"; + + internal const string SBServiceName = "SBServiceName"; + + internal const string SBServiceDescription = "SBServiceDescription"; + + internal const string ServiceControllerDesc = "ServiceControllerDesc"; + + internal const string SPCanPauseAndContinue = "SPCanPauseAndContinue"; + + internal const string SPCanShutdown = "SPCanShutdown"; + + internal const string SPCanStop = "SPCanStop"; + + internal const string SPDisplayName = "SPDisplayName"; + + internal const string SPDependentServices = "SPDependentServices"; + + internal const string SPMachineName = "SPMachineName"; + + internal const string SPServiceName = "SPServiceName"; + + internal const string SPServicesDependedOn = "SPServicesDependedOn"; + + internal const string SPStatus = "SPStatus"; + + internal const string SPServiceType = "SPServiceType"; + + internal const string SPStartType = "SPStartType"; + + internal const string ServiceProcessInstallerAccount = "ServiceProcessInstallerAccount"; + + internal const string ServiceInstallerDescription = "ServiceInstallerDescription"; + + internal const string ServiceInstallerServicesDependedOn = "ServiceInstallerServicesDependedOn"; + + internal const string ServiceInstallerServiceName = "ServiceInstallerServiceName"; + + internal const string ServiceInstallerStartType = "ServiceInstallerStartType"; + + internal const string ServiceInstallerDelayedAutoStart = "ServiceInstallerDelayedAutoStart"; + + internal const string ServiceInstallerDisplayName = "ServiceInstallerDisplayName"; + + internal const string Label_SetServiceLogin = "Label_SetServiceLogin"; + + internal const string Label_MissmatchedPasswords = "Label_MissmatchedPasswords"; + + private static Res loader; + + private ResourceManager resources; + + private static CultureInfo Culture + { + get + { + return null; + } + } + + public static ResourceManager Resources + { + get + { + return Res.GetLoader().resources; + } + } + + internal Res() + { + this.resources = new ResourceManager("System.ServiceProcess", base.GetType().Assembly); + } + + private static Res GetLoader() + { + if (Res.loader == null) + { + Res value = new Res(); + Interlocked.CompareExchange(ref Res.loader, value, (Res)null); + } + return Res.loader; + } + + public static string GetString(string name, params object[] args) + { + Res res = Res.GetLoader(); + if (res == null) + { + return null; + } + string @string = res.resources.GetString(name, Res.Culture); + if (args != null && args.Length != 0) + { + for (int i = 0; i < args.Length; i++) + { + string text = args[i] as string; + if (text != null && text.Length > 1024) + { + args[i] = text.Substring(0, 1021) + "..."; + } + } + return string.Format(CultureInfo.CurrentCulture, @string, args); + } + return @string; + } + + public static string GetString(string name) + { + Res res = Res.GetLoader(); + if (res == null) + { + return null; + } + return res.resources.GetString(name, Res.Culture); + } + + public static string GetString(string name, out bool usedFallback) + { + usedFallback = false; + return Res.GetString(name); + } + + public static object GetObject(string name) + { + Res res = Res.GetLoader(); + if (res == null) + { + return null; + } + return res.resources.GetObject(name, Res.Culture); + } + } +} diff --git a/ZeroLevel/Services/AsService/ResDescriptionAttribute.cs b/ZeroLevel/Services/AsService/ResDescriptionAttribute.cs new file mode 100644 index 0000000..fb8bb9a --- /dev/null +++ b/ZeroLevel/Services/AsService/ResDescriptionAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; + +namespace ZeroLevel.Services.AsService +{ + [AttributeUsage(AttributeTargets.All)] + internal sealed class ResDescriptionAttribute : DescriptionAttribute + { + private bool replaced; + + public override string Description + { + get + { + if (!this.replaced) + { + this.replaced = true; + base.DescriptionValue = Res.GetString(base.Description); + } + return base.Description; + } + } + + public ResDescriptionAttribute(string description) + : base(description) + { + } + } +} diff --git a/ZeroLevel/Services/AsService/SafeNativeMethods.cs b/ZeroLevel/Services/AsService/SafeNativeMethods.cs new file mode 100644 index 0000000..255ea4c --- /dev/null +++ b/ZeroLevel/Services/AsService/SafeNativeMethods.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; + +namespace ZeroLevel.Services.AsService +{ + [SuppressUnmanagedCodeSecurity] + public partial class SafeNativeMethods + { + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static extern bool CloseServiceHandle(IntPtr handle); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int LsaClose(IntPtr objectHandle); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int LsaNtStatusToWinError(int ntStatus); + + [DllImport("advapi32.dll")] + public static extern int LsaFreeMemory(IntPtr ptr); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr OpenSCManager(string machineName, string databaseName, int access); + + } +} diff --git a/ZeroLevel/Services/AsService/ServiceAccount.cs b/ZeroLevel/Services/AsService/ServiceAccount.cs new file mode 100644 index 0000000..ed731bb --- /dev/null +++ b/ZeroLevel/Services/AsService/ServiceAccount.cs @@ -0,0 +1,14 @@ +namespace ZeroLevel.Services.AsService +{ + public enum ServiceAccount + { + /// An account that acts as a non-privileged user on the local computer, and presents anonymous credentials to any remote server. + LocalService, + /// An account that provides extensive local privileges, and presents the computer's credentials to any remote server. + NetworkService, + /// An account, used by the service control manager, that has extensive privileges on the local computer and acts as the computer on the network. + LocalSystem, + /// An account defined by a specific user on the network. Specifying User for the member causes the system to prompt for a valid user name and password when the service is installed, unless you set values for both the and properties of your instance. + User + } +} diff --git a/ZeroLevel/Services/AsService/ServiceControlException.cs b/ZeroLevel/Services/AsService/ServiceControlException.cs new file mode 100644 index 0000000..411f86f --- /dev/null +++ b/ZeroLevel/Services/AsService/ServiceControlException.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.Serialization; + +namespace ZeroLevel.Services.AsService +{ + [Serializable] + public class ServiceControlException : + Exception + { + public ServiceControlException() + { + } + + public ServiceControlException(string message) + : base(message) + { + } + + public ServiceControlException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected ServiceControlException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public ServiceControlException(string format, Type serviceType, string command, Exception innerException) + : this(FormatMessage(format, serviceType, command), innerException) + { + + } + + static string FormatMessage(string format, Type serviceType, string command) + { + return string.Format(format, serviceType, command); + } + } +} diff --git a/ZeroLevel/Services/AsService/ServiceControllerExtensions.cs b/ZeroLevel/Services/AsService/ServiceControllerExtensions.cs new file mode 100644 index 0000000..03c527b --- /dev/null +++ b/ZeroLevel/Services/AsService/ServiceControllerExtensions.cs @@ -0,0 +1,16 @@ +namespace ZeroLevel.Services.AsService +{ + public static class ServiceControllerExtensions + { + public static bool ValidServiceName(string serviceName) + { + if (serviceName != null + && serviceName.Length <= 80 + && serviceName.Length != 0) + { + return serviceName.IndexOfAny(new[] { '\\', '/' }) == -1; + } + return false; + } + } +} diff --git a/ZeroLevel/Services/AsService/ServiceHandle.cs b/ZeroLevel/Services/AsService/ServiceHandle.cs new file mode 100644 index 0000000..484c33d --- /dev/null +++ b/ZeroLevel/Services/AsService/ServiceHandle.cs @@ -0,0 +1,66 @@ +using System; + +namespace ZeroLevel.Services.AsService +{ + /// + /// A handle to a service being hosted by the Host + /// + public interface ServiceHandle : + IDisposable + { + /// + /// Start the service + /// + /// + /// True if the service was started, otherwise false + bool Start(HostControl hostControl); + + /// + /// Pause the service + /// + /// + /// True if the service was paused, otherwise false + bool Pause(HostControl hostControl); + + /// + /// Continue the service from a paused state + /// + /// + /// True if the service was able to continue, otherwise false + bool Continue(HostControl hostControl); + + /// + /// Stop the service + /// + /// + /// True if the service was stopped, or false if the service cannot be stopped at this time + bool Stop(HostControl hostControl); + + /// + /// Handle the shutdown event + /// + /// + void Shutdown(HostControl hostControl); + + /// + /// Handle the session change event + /// + /// + /// + void SessionChanged(HostControl hostControl, SessionChangedArguments arguments); + + /// + /// Handle the power change event + /// + /// + /// + bool PowerEvent(HostControl hostControl, PowerEventArguments arguments); + + /// + /// Handle the custom command + /// + /// + /// + void CustomCommand(HostControl hostControl, int command); + } +} diff --git a/ZeroLevel/Services/AsService/SessionChangedArguments.cs b/ZeroLevel/Services/AsService/SessionChangedArguments.cs new file mode 100644 index 0000000..672c100 --- /dev/null +++ b/ZeroLevel/Services/AsService/SessionChangedArguments.cs @@ -0,0 +1,21 @@ +namespace ZeroLevel.Services.AsService +{ + public enum SessionChangeReasonCode + { + ConsoleConnect = 1, + ConsoleDisconnect = 2, + RemoteConnect = 3, + RemoteDisconnect = 4, + SessionLogon = 5, + SessionLogoff = 6, + SessionLock = 7, + SessionUnlock = 8, + SessionRemoteControl = 9, + } + + public interface SessionChangedArguments + { + SessionChangeReasonCode ReasonCode { get; } + int SessionId { get; } + } +} diff --git a/ZeroLevel/Services/AsService/System.Diagnostics/EventLogInstaller.cs b/ZeroLevel/Services/AsService/System.Diagnostics/EventLogInstaller.cs new file mode 100644 index 0000000..d76374c --- /dev/null +++ b/ZeroLevel/Services/AsService/System.Diagnostics/EventLogInstaller.cs @@ -0,0 +1,276 @@ +using Microsoft.Win32; +using System.Collections; +using System.ComponentModel; +using System.Runtime.InteropServices; +using ZeroLevel.Services.AsService; + +namespace System.Diagnostics +{ + public class EventLogInstaller + : ComponentInstaller + { + private EventSourceCreationData sourceData = new EventSourceCreationData(null, null); + + private UninstallAction uninstallAction; + + /// Gets or sets the path of the resource file that contains category strings for the source. + /// The path of the category resource file. The default is an empty string (""). + [TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [Editor("System.Windows.Forms.Design.FileNameEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [ComVisible(false)] + [ResDescription("Desc_CategoryResourceFile")] + public string CategoryResourceFile + { + get + { + return this.sourceData.CategoryResourceFile; + } + set + { + this.sourceData.CategoryResourceFile = value; + } + } + + /// Gets or sets the number of categories in the category resource file. + /// The number of categories in the category resource file. The default value is zero. + [ComVisible(false)] + [ResDescription("Desc_CategoryCount")] + public int CategoryCount + { + get + { + return this.sourceData.CategoryCount; + } + set + { + this.sourceData.CategoryCount = value; + } + } + + /// Gets or sets the name of the log to set the source to. + /// The name of the log. This can be Application, System, or a custom log name. The default is an empty string (""). + [TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [ResDescription("Desc_Log")] + public string Log + { + get + { + if (this.sourceData.LogName == null && this.sourceData.Source != null) + { + this.sourceData.LogName = EventLog.LogNameFromSourceName(this.sourceData.Source, "."); + } + return this.sourceData.LogName; + } + set + { + this.sourceData.LogName = value; + } + } + + /// Gets or sets the path of the resource file that contains message formatting strings for the source. + /// The path of the message resource file. The default is an empty string (""). + [TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [Editor("System.Windows.Forms.Design.FileNameEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [ComVisible(false)] + [ResDescription("Desc_MessageResourceFile")] + public string MessageResourceFile + { + get + { + return this.sourceData.MessageResourceFile; + } + set + { + this.sourceData.MessageResourceFile = value; + } + } + + /// Gets or sets the path of the resource file that contains message parameter strings for the source. + /// The path of the message parameter resource file. The default is an empty string (""). + [TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [Editor("System.Windows.Forms.Design.FileNameEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [ComVisible(false)] + [ResDescription("Desc_ParameterResourceFile")] + public string ParameterResourceFile + { + get + { + return this.sourceData.ParameterResourceFile; + } + set + { + this.sourceData.ParameterResourceFile = value; + } + } + + /// Gets or sets the source name to register with the log. + /// The name to register with the event log as a source of entries. The default is an empty string (""). + [TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [ResDescription("Desc_Source")] + public string Source + { + get + { + return this.sourceData.Source; + } + set + { + this.sourceData.Source = value; + } + } + + /// Gets or sets a value that indicates whether the Installutil.exe (Installer Tool) should remove the event log or leave it in its installed state at uninstall time. + /// One of the values that indicates what state to leave the event log in when the is uninstalled. The default is Remove. + /// + /// contains an invalid value. The only valid values for this property are Remove and NoAction. + [DefaultValue(UninstallAction.Remove)] + [ResDescription("Desc_UninstallAction")] + public UninstallAction UninstallAction + { + get + { + return this.uninstallAction; + } + set + { + if (!Enum.IsDefined(typeof(UninstallAction), value)) + { + throw new InvalidEnumArgumentException("value", (int)value, typeof(UninstallAction)); + } + this.uninstallAction = value; + } + } + + /// Copies the property values of an component that are required at installation time for an event log. + /// An to use as a template for the . + /// The specified component is not an .-or- The or property of the specified component is either null or empty. + public override void CopyFromComponent(IComponent component) + { + EventLog eventLog = component as EventLog; + if (eventLog == null) + { + throw new ArgumentException(Res.GetString("NotAnEventLog")); + } + if (eventLog.Log != null && !(eventLog.Log == string.Empty) && eventLog.Source != null && !(eventLog.Source == string.Empty)) + { + this.Log = eventLog.Log; + this.Source = eventLog.Source; + return; + } + throw new ArgumentException(Res.GetString("IncompleteEventLog")); + } + + /// Performs the installation and writes event log information to the registry. + /// An used to save information needed to perform a rollback or uninstall operation. + /// The platform the installer is trying to use is not Windows NT 4.0 or later. + /// The name specified in the property is already registered for a different event log. + public override void Install(IDictionary stateSaver) + { + base.Install(stateSaver); + base.Context.LogMessage(Res.GetString("CreatingEventLog", this.Source, this.Log)); + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + throw new PlatformNotSupportedException(Res.GetString("WinNTRequired")); + } + stateSaver["baseInstalledAndPlatformOK"] = true; + bool flag = EventLog.Exists(this.Log, "."); + stateSaver["logExists"] = flag; + bool flag2 = EventLog.SourceExists(this.Source, "."); + stateSaver["alreadyRegistered"] = flag2; + if (flag2 && EventLog.LogNameFromSourceName(this.Source, ".") == this.Log) + { + return; + } + EventLog.CreateEventSource(this.sourceData); + } + + /// Determines whether an installer and another specified installer refer to the same source. + /// true if this installer and the installer specified by the parameter would install or uninstall the same source; otherwise, false. + /// The installer to compare. + public override bool IsEquivalentInstaller(ComponentInstaller otherInstaller) + { + EventLogInstaller eventLogInstaller = otherInstaller as EventLogInstaller; + if (eventLogInstaller == null) + { + return false; + } + return eventLogInstaller.Source == this.Source; + } + + /// Restores the computer to the state it was in before the installation by rolling back the event log information that the installation procedure wrote to the registry. + /// An that contains the pre-installation state of the computer. + public override void Rollback(IDictionary savedState) + { + base.Rollback(savedState); + base.Context.LogMessage(Res.GetString("RestoringEventLog", this.Source)); + if (savedState["baseInstalledAndPlatformOK"] != null) + { + if (!(bool)savedState["logExists"]) + { + EventLog.Delete(this.Log, "."); + } + else + { + object obj = savedState["alreadyRegistered"]; + bool flag = obj != null && (bool)obj; + if (!flag && EventLog.SourceExists(this.Source, ".")) + { + EventLog.DeleteEventSource(this.Source, "."); + } + } + } + } + + /// Removes an installation by removing event log information from the registry. + /// An that contains the pre-installation state of the computer. + public override void Uninstall(IDictionary savedState) + { + base.Uninstall(savedState); + if (this.UninstallAction == UninstallAction.Remove) + { + base.Context.LogMessage(Res.GetString("RemovingEventLog", this.Source)); + if (EventLog.SourceExists(this.Source, ".")) + { + if (string.Compare(this.Log, this.Source, StringComparison.OrdinalIgnoreCase) != 0) + { + EventLog.DeleteEventSource(this.Source, "."); + } + } + else + { + base.Context.LogMessage(Res.GetString("LocalSourceNotRegisteredWarning", this.Source)); + } + RegistryKey registryKey = Registry.LocalMachine; + RegistryKey registryKey2 = null; + try + { + registryKey = registryKey.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\EventLog", false); + if (registryKey != null) + { + registryKey2 = registryKey.OpenSubKey(this.Log, false); + } + if (registryKey2 != null) + { + string[] subKeyNames = registryKey2.GetSubKeyNames(); + if (subKeyNames == null || subKeyNames.Length == 0 || (subKeyNames.Length == 1 && string.Compare(subKeyNames[0], this.Log, StringComparison.OrdinalIgnoreCase) == 0)) + { + base.Context.LogMessage(Res.GetString("DeletingEventLog", this.Log)); + EventLog.Delete(this.Log, "."); + } + } + } + finally + { + if (registryKey != null) + { + registryKey.Close(); + } + if (registryKey2 != null) + { + registryKey2.Close(); + } + } + } + } + } +} \ No newline at end of file diff --git a/ZeroLevel/Services/AsService/System.Diagnostics/UninstallAction.cs b/ZeroLevel/Services/AsService/System.Diagnostics/UninstallAction.cs new file mode 100644 index 0000000..b4a776b --- /dev/null +++ b/ZeroLevel/Services/AsService/System.Diagnostics/UninstallAction.cs @@ -0,0 +1,11 @@ +namespace System.Diagnostics +{ + /// Specifies what an installer should do during an uninstallation. + public enum UninstallAction + { + /// Removes the resource the installer created. + Remove, + /// Leaves the resource created by the installer as is. + NoAction + } +} diff --git a/ZeroLevel/Services/AsService/Windows/HostInstaller.cs b/ZeroLevel/Services/AsService/Windows/HostInstaller.cs new file mode 100644 index 0000000..3f841c1 --- /dev/null +++ b/ZeroLevel/Services/AsService/Windows/HostInstaller.cs @@ -0,0 +1,56 @@ +using Microsoft.Win32; +using System.Collections; + +namespace ZeroLevel.Services.AsService +{ + public class HostInstaller : + Installer + { + readonly string _arguments; + readonly Installer[] _installers; + readonly HostSettings _settings; + + public HostInstaller(HostSettings settings, string arguments, Installer[] installers) + { + _installers = installers; + _arguments = arguments; + _settings = settings; + } + + public override void Install(IDictionary stateSaver) + { + Installers.AddRange(_installers); + + Log.Info("Installing {0} service", _settings.DisplayName); + + base.Install(stateSaver); + + Log.Debug("Open Registry"); + using (RegistryKey system = Registry.LocalMachine.OpenSubKey("System")) + using (RegistryKey currentControlSet = system.OpenSubKey("CurrentControlSet")) + using (RegistryKey services = currentControlSet.OpenSubKey("Services")) + using (RegistryKey service = services.OpenSubKey(_settings.ServiceName, true)) + { + service.SetValue("Description", _settings.Description); + + var imagePath = (string)service.GetValue("ImagePath"); + + Log.Debug("Service path: {0}", imagePath); + + imagePath += _arguments; + + Log.Debug("Image path: {0}", imagePath); + + service.SetValue("ImagePath", imagePath); + } + Log.Debug("Closing Registry"); + } + + public override void Uninstall(IDictionary savedState) + { + Installers.AddRange(_installers); + Log.Info("Uninstalling {0} service", _settings.Name); + base.Uninstall(savedState); + } + } +} diff --git a/ZeroLevel/Services/AsService/Windows/HostServiceInstaller.cs b/ZeroLevel/Services/AsService/Windows/HostServiceInstaller.cs new file mode 100644 index 0000000..e464f97 --- /dev/null +++ b/ZeroLevel/Services/AsService/Windows/HostServiceInstaller.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.ServiceProcess; +using System.Text; + +namespace ZeroLevel.Services.AsService +{ + public class HostServiceInstaller : + IDisposable + { + readonly Installer _installer; + readonly TransactedInstaller _transactedInstaller; + public ServiceProcessInstaller ServiceProcessInstaller + { + get + { + return (ServiceProcessInstaller)_installer.Installers[1]; + } + } + + public HostServiceInstaller(InstallHostSettings settings) + { + _installer = CreateInstaller(settings); + + _transactedInstaller = CreateTransactedInstaller(_installer); + } + + public HostServiceInstaller(HostSettings settings) + { + _installer = CreateInstaller(settings); + + _transactedInstaller = CreateTransactedInstaller(_installer); + } + + public void Dispose() + { + try + { + _transactedInstaller.Dispose(); + } + finally + { + _installer.Dispose(); + } + } + + public void InstallService(Action beforeInstall, Action afterInstall, Action beforeRollback, Action afterRollback) + { + if (beforeInstall != null) + _installer.BeforeInstall += (sender, args) => beforeInstall(args); + if (afterInstall != null) + _installer.AfterInstall += (sender, args) => afterInstall(args); + if (beforeRollback != null) + _installer.BeforeRollback += (sender, args) => beforeRollback(args); + if (afterRollback != null) + _installer.AfterRollback += (sender, args) => afterRollback(args); + + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + + _transactedInstaller.Install(new Hashtable()); + } + + public void UninstallService(Action beforeUninstall, Action afterUninstall) + { + if (beforeUninstall != null) + _installer.BeforeUninstall += (sender, args) => beforeUninstall(args); + if (afterUninstall != null) + _installer.AfterUninstall += (sender, args) => afterUninstall(args); + + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + + _transactedInstaller.Uninstall(null); + } + + static Installer CreateInstaller(InstallHostSettings settings) + { + var installers = new Installer[] + { + ConfigureServiceInstaller(settings, settings.Dependencies, settings.StartMode), + ConfigureServiceProcessInstaller(settings.Credentials.Account, settings.Credentials.Username, settings.Credentials.Password) + }; + + //DO not auto create EventLog Source while install service + //MSDN: When the installation is performed, it automatically creates an EventLogInstaller to install the event log source associated with the ServiceBase derived class. The Log property for this source is set by the ServiceInstaller constructor to the computer's Application log. When you set the ServiceName of the ServiceInstaller (which should be identical to the ServiceBase..::.ServiceName of the service), the Source is automatically set to the same value. In an installation failure, the source's installation is rolled-back along with previously installed services. + //MSDN: from EventLog.CreateEventSource Method (String, String) : an ArgumentException thrown when The first 8 characters of logName match the first 8 characters of an existing event log name. + RemoveEventLogInstallers(installers); + + return CreateHostInstaller(settings, installers); + } + + private static void RemoveEventLogInstallers(Installer[] installers) + { + foreach (var installer in installers) + { + var eventLogInstallers = installer.Installers.OfType().ToArray(); + foreach (var eventLogInstaller in eventLogInstallers) + { + installer.Installers.Remove(eventLogInstaller); + } + } + } + + Installer CreateInstaller(HostSettings settings) + { + var installers = new Installer[] + { + ConfigureServiceInstaller(settings, new string[] {}, HostStartMode.Automatic), + ConfigureServiceProcessInstaller(ServiceAccount.LocalService, "", ""), + }; + + RemoveEventLogInstallers(installers); + + return CreateHostInstaller(settings, installers); + } + + static Installer CreateHostInstaller(HostSettings settings, Installer[] installers) + { + string arguments = " "; + + if (!string.IsNullOrEmpty(settings.InstanceName)) + arguments += $" -instance \"{settings.InstanceName}\""; + + if (!string.IsNullOrEmpty(settings.DisplayName)) + arguments += $" -displayname \"{settings.DisplayName}\""; + + if (!string.IsNullOrEmpty(settings.Name)) + arguments += $" -servicename \"{settings.Name}\""; + + return new HostInstaller(settings, arguments, installers); + } + + static TransactedInstaller CreateTransactedInstaller(Installer installer) + { + var transactedInstaller = new TransactedInstaller(); + + transactedInstaller.Installers.Add(installer); + + Assembly assembly = Assembly.GetEntryAssembly(); + + Process currentProcess = Process.GetCurrentProcess(); + + if (assembly == null) + throw new Exception("Assembly.GetEntryAssembly() is null for some reason."); + + if (currentProcess == null) + throw new Exception("Process.GetCurrentProcess() is null for some reason."); + + string path = + IsDotnetExe(currentProcess) + ? $"/assemblypath={currentProcess.MainModule.FileName} \"{assembly.Location}\"" + : $"/assemblypath={currentProcess.MainModule.FileName}"; + + string[] commandLine = { path }; + + var context = new InstallContext(null, commandLine); + transactedInstaller.Context = context; + + return transactedInstaller; + } + + static bool IsDotnetExe(Process process) => + process + .MainModule + .ModuleName + .Equals("dotnet.exe", StringComparison.InvariantCultureIgnoreCase); + + static ServiceInstaller ConfigureServiceInstaller(HostSettings settings, string[] dependencies, + HostStartMode startMode) + { + var installer = new ServiceInstaller + { + ServiceName = settings.ServiceName, + Description = settings.Description, + DisplayName = settings.DisplayName, + ServicesDependedOn = dependencies + }; + + SetStartMode(installer, startMode); + + return installer; + } + + static void SetStartMode(ServiceInstaller installer, HostStartMode startMode) + { + switch (startMode) + { + case HostStartMode.Automatic: + installer.StartType = ServiceStartMode.Automatic; + break; + + case HostStartMode.Manual: + installer.StartType = ServiceStartMode.Manual; + break; + + case HostStartMode.Disabled: + installer.StartType = ServiceStartMode.Disabled; + break; + + case HostStartMode.AutomaticDelayed: + installer.StartType = ServiceStartMode.Automatic; + installer.DelayedAutoStart = true; + break; + } + } + + static ServiceProcessInstaller ConfigureServiceProcessInstaller(ServiceAccount account, string username, + string password) + { + var installer = new ServiceProcessInstaller + { + Username = username, + Password = password, + Account = account, + }; + + return installer; + } + } +} diff --git a/ZeroLevel/Services/AsService/Windows/Kernel32.cs b/ZeroLevel/Services/AsService/Windows/Kernel32.cs new file mode 100644 index 0000000..beb51c2 --- /dev/null +++ b/ZeroLevel/Services/AsService/Windows/Kernel32.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace ZeroLevel.Services.AsService +{ + static class Kernel32 + { + public static uint TH32CS_SNAPPROCESS = 2; + + [StructLayout(LayoutKind.Sequential)] + public struct PROCESSENTRY32 + { + public uint dwSize; + public uint cntUsage; + public uint th32ProcessID; + public IntPtr th32DefaultHeapID; + public uint th32ModuleID; + public uint cntThreads; + public uint th32ParentProcessID; + public int pcPriClassBase; + public uint dwFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szExeFile; + }; + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID); + + [DllImport("kernel32.dll")] + public static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe); + + [DllImport("kernel32.dll")] + public static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe); + } +} diff --git a/ZeroLevel/Services/AsService/Windows/NativeMethods.cs b/ZeroLevel/Services/AsService/Windows/NativeMethods.cs new file mode 100644 index 0000000..62068db --- /dev/null +++ b/ZeroLevel/Services/AsService/Windows/NativeMethods.cs @@ -0,0 +1,91 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace ZeroLevel.Services.AsService +{ + public partial class NativeMethods + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class LSA_UNICODE_STRING_withPointer + { + public short length; + + public short maximumLength; + + public IntPtr pwstr = (IntPtr)0; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class LSA_UNICODE_STRING + { + public short length; + + public short maximumLength; + + public string buffer; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class LSA_OBJECT_ATTRIBUTES + { + public int length; + + public IntPtr rootDirectory = (IntPtr)0; + + public IntPtr pointerLsaString = (IntPtr)0; + + public int attributes; + + public IntPtr pointerSecurityDescriptor = (IntPtr)0; + + public IntPtr pointerSecurityQualityOfService = (IntPtr)0; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SERVICE_DELAYED_AUTOSTART_INFO + { + public bool fDelayedAutostart; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SERVICE_DESCRIPTION + { + public IntPtr description; + } + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DESCRIPTION serviceDesc); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DELAYED_AUTOSTART_INFO serviceDesc); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr CreateService(IntPtr databaseHandle, string serviceName, string displayName, int access, int serviceType, int startType, int errorControl, string binaryPath, string loadOrderGroup, IntPtr pTagId, string dependencies, string servicesStartName, string password); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool LookupAccountName(string systemName, string accountName, byte[] sid, int[] sidLen, char[] refDomainName, int[] domNameLen, [In] [Out] int[] sidNameUse); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int LsaAddAccountRights(IntPtr policyHandle, byte[] accountSid, LSA_UNICODE_STRING userRights, int countOfRights); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int LsaEnumerateAccountRights(IntPtr policyHandle, byte[] accountSid, out IntPtr pLsaUnicodeStringUserRights, out int RightsCount); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int LsaOpenPolicy(LSA_UNICODE_STRING systemName, IntPtr pointerObjectAttributes, int desiredAccess, out IntPtr pointerPolicyHandle); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int LsaRemoveAccountRights(IntPtr policyHandle, byte[] accountSid, bool allRights, LSA_UNICODE_STRING userRights, int countOfRights); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool GetComputerName(StringBuilder lpBuffer, ref int nSize); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool DeleteService(IntPtr serviceHandle); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr OpenService(IntPtr databaseHandle, string serviceName, int access); + + } +} diff --git a/ZeroLevel/Services/AsService/Windows/SafeTokenHandle.cs b/ZeroLevel/Services/AsService/Windows/SafeTokenHandle.cs new file mode 100644 index 0000000..f1e917b --- /dev/null +++ b/ZeroLevel/Services/AsService/Windows/SafeTokenHandle.cs @@ -0,0 +1,26 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; + +namespace ZeroLevel.Services.AsService +{ + public sealed class SafeTokenHandle + : SafeHandleZeroOrMinusOneIsInvalid + { + private SafeTokenHandle() + : base(true) { } + + [DllImport("kernel32.dll")] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [SuppressUnmanagedCodeSecurity] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr handle); + + protected override bool ReleaseHandle() + { + return CloseHandle(handle); + } + } +} diff --git a/ZeroLevel/Services/AsService/Windows/WindowsServiceHost.cs b/ZeroLevel/Services/AsService/Windows/WindowsServiceHost.cs new file mode 100644 index 0000000..7a34151 --- /dev/null +++ b/ZeroLevel/Services/AsService/Windows/WindowsServiceHost.cs @@ -0,0 +1,352 @@ +using System; +using System.IO; +using System.Reflection; +using System.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; + +namespace ZeroLevel.Services.AsService.Windows +{ + public class WindowsServiceHost : + ServiceBase, + Host, + HostControl + { + readonly HostConfigurator _configurator; + readonly HostEnvironment _environment; + readonly ServiceHandle _serviceHandle; + readonly HostSettings _settings; + int _deadThread; + bool _disposed; + Exception _unhandledException; + + public WindowsServiceHost(HostEnvironment environment, HostSettings settings, ServiceHandle serviceHandle, HostConfigurator configurator) + { + if (settings == null) + throw new ArgumentNullException(nameof(settings)); + if (serviceHandle == null) + throw new ArgumentNullException(nameof(serviceHandle)); + + _settings = settings; + _serviceHandle = serviceHandle; + _environment = environment; + _configurator = configurator; + + CanPauseAndContinue = settings.CanPauseAndContinue; + CanShutdown = settings.CanShutdown; + CanHandlePowerEvent = settings.CanHandlePowerEvent; + CanHandleSessionChangeEvent = settings.CanSessionChanged; + ServiceName = _settings.ServiceName; + } + + public ExitCode Run() + { + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + + AppDomain.CurrentDomain.UnhandledException += CatchUnhandledException; + + ExitCode = (int)AsService.ExitCode.Ok; + + Log.Info("Starting as a Windows service"); + + if (!_environment.IsServiceInstalled(_settings.ServiceName)) + { + string message = $"The {_settings.ServiceName} service has not been installed yet. Please run '{Assembly.GetEntryAssembly().GetName()} install'."; + Log.Fatal(message); + + ExitCode = (int)AsService.ExitCode.ServiceNotInstalled; + throw new Exception(message); + } + + Log.Debug("[AsService] Starting up as a windows service application"); + + Run(this); + + return (ExitCode)Enum.ToObject(typeof(ExitCode), ExitCode); + } + + void HostControl.RequestAdditionalTime(TimeSpan timeRemaining) + { + Log.Debug("Requesting additional time: {0}", timeRemaining); + + RequestAdditionalTime((int)timeRemaining.TotalMilliseconds); + } + + void HostControl.Stop() + { + InternalStop(); + } + + void HostControl.Stop(ExitCode exitCode) + { + InternalStop(exitCode); + } + + void InternalStop(ExitCode? exitCode = null) + { + if (CanStop) + { + Log.Debug("Stop requested by hosted service"); + if (exitCode.HasValue) + ExitCode = (int)exitCode.Value; + Stop(); + } + else + { + Log.Debug("Stop requested by hosted service, but service cannot be stopped at this time"); + throw new ServiceControlException("The service cannot be stopped at this time"); + } + } + + protected override void OnStart(string[] args) + { + try + { + Log.Info("[AsService] Starting"); + + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + + Log.Debug("[AsService] Current Directory: {0}", Directory.GetCurrentDirectory()); + + Log.Debug("[AsService] Arguments: {0}", string.Join(",", args)); + + string startArgs = string.Join(" ", args); + _configurator.ApplyCommandLine(startArgs); + + if (!_serviceHandle.Start(this)) + throw new Exception("The service did not start successfully (returned false)."); + + Log.Info("[AsService] Started"); + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Fatal("The service did not start successfully", ex); + + ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed; + throw; + } + } + + protected override void OnStop() + { + try + { + Log.Info("[AsService] Stopping"); + + if (!_serviceHandle.Stop(this)) + throw new Exception("The service did not stop successfully (return false)."); + + Log.Info("[AsService] Stopped"); + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Fatal("The service did not shut down gracefully", ex); + ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed; + throw; + } + + if (_unhandledException != null) + { + ExitCode = (int)AsService.ExitCode.AbnormalExit; + Log.Info("[AsService] Unhandled exception detected, rethrowing to cause application to restart."); + throw new InvalidOperationException("An unhandled exception was detected", _unhandledException); + } + } + + protected override void OnPause() + { + try + { + Log.Info("[AsService] Pausing service"); + + if (!_serviceHandle.Pause(this)) + throw new Exception("The service did not pause successfully (returned false)."); + + Log.Info("[AsService] Paused"); + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Fatal("The service did not pause gracefully", ex); + throw; + } + } + + protected override void OnContinue() + { + try + { + Log.Info("[AsService] Resuming service"); + + if (!_serviceHandle.Continue(this)) + throw new Exception("The service did not continue successfully (returned false)."); + + Log.Info("[AsService] Resumed"); + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Fatal("The service did not resume successfully", ex); + throw; + } + } + + protected override void OnShutdown() + { + try + { + Log.Info("[AsService] Service is being shutdown"); + + _serviceHandle.Shutdown(this); + + Log.Info("[AsService] Stopped"); + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Fatal("The service did not shut down gracefully", ex); + ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed; + throw; + } + } + + protected override void OnSessionChange(SessionChangeDescription changeDescription) + { + try + { + Log.Info("[AsService] Service session changed"); + + var arguments = new WindowsSessionChangedArguments(changeDescription); + + _serviceHandle.SessionChanged(this, arguments); + + Log.Info("[AsService] Service session changed handled"); + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Fatal("The did not handle Service session change correctly", ex); + ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed; + throw; + } + } + + protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) + { + try + { + Log.Info("[AsService] Power event raised"); + + var arguments = new WindowsPowerEventArguments(powerStatus); + + bool result = _serviceHandle.PowerEvent(this, arguments); + + Log.Info("[AsService] Power event handled"); + + return result; + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Fatal("The service did handle the Power event correctly", ex); + ExitCode = (int)AsService.ExitCode.ServiceControlRequestFailed; + throw; + } + } + + protected override void OnCustomCommand(int command) + { + try + { + Log.Info("[AsService] Custom command {0} received", command); + + _serviceHandle.CustomCommand(this, command); + + Log.Info("[AsService] Custom command {0} processed", command); + } + catch (Exception ex) + { + _settings.ExceptionCallback?.Invoke(ex); + + Log.Error("Unhandled exception during custom command processing detected", ex); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _serviceHandle?.Dispose(); + + _disposed = true; + } + + base.Dispose(disposing); + } + + void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + _settings.ExceptionCallback?.Invoke((Exception)e.ExceptionObject); + + if (_settings.UnhandledExceptionPolicy == UnhandledExceptionPolicyCode.TakeNoAction) + return; + + Log.Fatal("The service threw an unhandled exception", (Exception)e.ExceptionObject); + + if (_settings.UnhandledExceptionPolicy == UnhandledExceptionPolicyCode.LogErrorOnly) + return; + + ExitCode = (int)AsService.ExitCode.AbnormalExit; + _unhandledException = (Exception)e.ExceptionObject; + + Stop(); + + + // it isn't likely that a TPL thread should land here, but if it does let's no block it + if (Task.CurrentId.HasValue) + return; + + int deadThreadId = Interlocked.Increment(ref _deadThread); + Thread.CurrentThread.IsBackground = true; + Thread.CurrentThread.Name = "Unhandled Exception " + deadThreadId; + while (true) + Thread.Sleep(TimeSpan.FromHours(1)); + } + + + class WindowsSessionChangedArguments : + SessionChangedArguments + { + public WindowsSessionChangedArguments(SessionChangeDescription changeDescription) + { + ReasonCode = (SessionChangeReasonCode)Enum.ToObject(typeof(SessionChangeReasonCode), (int)changeDescription.Reason); + SessionId = changeDescription.SessionId; + } + + public SessionChangeReasonCode ReasonCode { get; } + + public int SessionId { get; } + } + + class WindowsPowerEventArguments : + PowerEventArguments + { + public WindowsPowerEventArguments(PowerBroadcastStatus powerStatus) + { + EventCode = (PowerEventCode)Enum.ToObject(typeof(PowerEventCode), (int)powerStatus); + } + + + public PowerEventCode EventCode { get; } + } + } +} diff --git a/ZeroLevel/Services/AsService/as_delegates.cs b/ZeroLevel/Services/AsService/as_delegates.cs new file mode 100644 index 0000000..2b2a1d2 --- /dev/null +++ b/ZeroLevel/Services/AsService/as_delegates.cs @@ -0,0 +1,8 @@ +using ZeroLevel.Services.AsService.Builder; + +namespace ZeroLevel.Services.AsService +{ + public delegate HostBuilder HostBuilderFactory(HostEnvironment environment, HostSettings settings); + public delegate ServiceBuilder ServiceBuilderFactory(HostSettings settings); + public delegate EnvironmentBuilder EnvironmentBuilderFactory(HostConfigurator configurator); +} diff --git a/ZeroLevel/ZeroLevel.csproj b/ZeroLevel/ZeroLevel.csproj index 168b95a..8cc5f92 100644 --- a/ZeroLevel/ZeroLevel.csproj +++ b/ZeroLevel/ZeroLevel.csproj @@ -35,4 +35,18 @@ true + + + + + + + C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.win32.registry\4.5.0\ref\netstandard2.0\Microsoft.Win32.Registry.dll + + + + + + +