From e8e1ee230068aa3ed2f54057d37660f45baef13b Mon Sep 17 00:00:00 2001
From: NextTurn <45985406+NextTurn@users.noreply.github.com>
Date: Tue, 29 Oct 2019 00:00:00 +0800
Subject: [PATCH 1/2] Improve CLI behaviors with empty arguments
---
src/Core/ServiceWrapper/Main.cs | 500 ++++++++++++++++----------------
1 file changed, 252 insertions(+), 248 deletions(-)
diff --git a/src/Core/ServiceWrapper/Main.cs b/src/Core/ServiceWrapper/Main.cs
index d15e0dd..26e2bb5 100644
--- a/src/Core/ServiceWrapper/Main.cs
+++ b/src/Core/ServiceWrapper/Main.cs
@@ -479,300 +479,304 @@ namespace winsw
///
/// Runs the wrapper.
///
- /// Arguments. If empty, WinSW will behave in the service mode. Otherwise - CLI mode
+ /// Arguments.
/// Service descriptor. If null, it will be initialized within the method.
/// In such case configs will be loaded from the XML Configuration File.
/// Any unhandled exception
public static void Run(string[] _args, ServiceDescriptor? descriptor = null)
{
- bool isCLIMode = _args.Length > 0;
+ bool inCliMode = Console.OpenStandardInput() != Stream.Null;
// If descriptor is not specified, initialize the new one (and load configs from there)
descriptor ??= new ServiceDescriptor();
// Configure the wrapper-internal logging.
// STDIN and STDOUT of the child process will be handled independently.
- InitLoggers(descriptor, isCLIMode);
+ InitLoggers(descriptor, inCliMode);
- if (isCLIMode) // CLI mode, in-service mode otherwise
+ if (!inCliMode)
{
- Log.Debug("Starting ServiceWrapper in the CLI mode");
+ Log.Info("Starting WinSW in the service mode");
+ Run(new WrapperService(descriptor));
+ return;
+ }
- // Get service info for the future use
- Win32Services svc = new WmiRoot().GetCollection();
- Win32Service s = svc.Select(descriptor.Id);
+ Log.Debug("Starting WinSW in the CLI mode");
- var args = new List(Array.AsReadOnly(_args));
- if (args[0] == "/redirect")
+ if (_args.Length == 0)
+ {
+ printHelp();
+ return;
+ }
+
+ // Get service info for the future use
+ Win32Services svc = new WmiRoot().GetCollection();
+ Win32Service s = svc.Select(descriptor.Id);
+
+ var args = new List(Array.AsReadOnly(_args));
+ if (args[0] == "/redirect")
+ {
+ // Redirect output
+ // One might ask why we support this when the caller
+ // can redirect the output easily. The answer is for supporting UAC.
+ // On UAC-enabled Windows such as Vista, SCM operation requires
+ // elevated privileges, thus winsw.exe needs to be launched
+ // accordingly. This in turn limits what the caller can do,
+ // and among other things it makes it difficult for the caller
+ // to read stdout/stderr. Thus redirection becomes handy.
+ var f = new FileStream(args[1], FileMode.Create);
+ var w = new StreamWriter(f) { AutoFlush = true };
+ Console.SetOut(w);
+ Console.SetError(w);
+
+ var handle = f.SafeFileHandle;
+ Kernel32.SetStdHandle(-11, handle); // set stdout
+ Kernel32.SetStdHandle(-12, handle); // set stder
+
+ args = args.GetRange(2, args.Count - 2);
+ }
+
+ args[0] = args[0].ToLower();
+ if (args[0] == "install")
+ {
+ Log.Info("Installing the service with id '" + descriptor.Id + "'");
+
+ // Check if the service exists
+ if (s != null)
{
- // Redirect output
- // One might ask why we support this when the caller
- // can redirect the output easily. The answer is for supporting UAC.
- // On UAC-enabled Windows such as Vista, SCM operation requires
- // elevated privileges, thus winsw.exe needs to be launched
- // accordingly. This in turn limits what the caller can do,
- // and among other things it makes it difficult for the caller
- // to read stdout/stderr. Thus redirection becomes handy.
- var f = new FileStream(args[1], FileMode.Create);
- var w = new StreamWriter(f) { AutoFlush = true };
- Console.SetOut(w);
- Console.SetError(w);
-
- var handle = f.SafeFileHandle;
- Kernel32.SetStdHandle(-11, handle); // set stdout
- Kernel32.SetStdHandle(-12, handle); // set stder
-
- args = args.GetRange(2, args.Count - 2);
+ Console.WriteLine("Service with id '" + descriptor.Id + "' already exists");
+ Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
+ throw new Exception("Installation failure: Service with id '" + descriptor.Id + "' already exists");
}
- args[0] = args[0].ToLower();
- if (args[0] == "install")
+ string? username = null;
+ string? password = null;
+ bool setallowlogonasaserviceright = false; // This variable is very readable.
+ if (args.Count > 1 && args[1] == "/p")
{
- Log.Info("Installing the service with id '" + descriptor.Id + "'");
-
- // Check if the service exists
- if (s != null)
+ // we expected username/password on stdin
+ Console.Write("Username: ");
+ username = Console.ReadLine();
+ Console.Write("Password: ");
+ password = ReadPassword();
+ Console.WriteLine();
+ Console.Write("Set Account rights to allow log on as a service (y/n)?: ");
+ var keypressed = Console.ReadKey();
+ Console.WriteLine();
+ if (keypressed.Key == ConsoleKey.Y)
{
- Console.WriteLine("Service with id '" + descriptor.Id + "' already exists");
- Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
- throw new Exception("Installation failure: Service with id '" + descriptor.Id + "' already exists");
+ setallowlogonasaserviceright = true;
+ }
+ }
+ else
+ {
+ if (descriptor.HasServiceAccount())
+ {
+ username = descriptor.ServiceAccountUser;
+ password = descriptor.ServiceAccountPassword;
+ setallowlogonasaserviceright = descriptor.AllowServiceAcountLogonRight;
+ }
+ }
+
+ if (setallowlogonasaserviceright)
+ {
+ LogonAsAService.AddLogonAsAServiceRight(username!);
+ }
+
+ svc.Create(
+ descriptor.Id,
+ descriptor.Caption,
+ "\"" + descriptor.ExecutablePath + "\"",
+ ServiceType.OwnProcess,
+ ErrorControl.UserNotified,
+ descriptor.StartMode,
+ descriptor.Interactive,
+ username,
+ password,
+ descriptor.ServiceDependencies);
+
+ // update the description
+ /* Somehow this doesn't work, even though it doesn't report an error
+ Win32Service s = svc.Select(d.Id);
+ s.Description = d.Description;
+ s.Commit();
+ */
+
+ // so using a classic method to set the description. Ugly.
+ Registry.LocalMachine
+ .OpenSubKey("System")
+ .OpenSubKey("CurrentControlSet")
+ .OpenSubKey("Services")
+ .OpenSubKey(descriptor.Id, true)
+ .SetValue("Description", descriptor.Description);
+
+ var actions = descriptor.FailureActions;
+ var isDelayedAutoStart = descriptor.StartMode == StartMode.Automatic && descriptor.DelayedAutoStart;
+ if (actions.Count > 0 || isDelayedAutoStart)
+ {
+ using ServiceManager scm = new ServiceManager();
+ using Service sc = scm.Open(descriptor.Id);
+
+ // Delayed auto start
+ if (isDelayedAutoStart)
+ {
+ sc.SetDelayedAutoStart(true);
}
- string? username = null;
- string? password = null;
- bool setallowlogonasaserviceright = false; // This variable is very readable.
- if (args.Count > 1 && args[1] == "/p")
+ // Set the failure actions
+ if (actions.Count > 0)
{
- // we expected username/password on stdin
- Console.Write("Username: ");
- username = Console.ReadLine();
- Console.Write("Password: ");
- password = ReadPassword();
- Console.WriteLine();
- Console.Write("Set Account rights to allow log on as a service (y/n)?: ");
- var keypressed = Console.ReadKey();
- Console.WriteLine();
- if (keypressed.Key == ConsoleKey.Y)
- {
- setallowlogonasaserviceright = true;
- }
+ sc.ChangeConfig(descriptor.ResetFailureAfter, actions);
+ }
+ }
+
+ return;
+ }
+
+ if (args[0] == "uninstall")
+ {
+ Log.Info("Uninstalling the service with id '" + descriptor.Id + "'");
+ if (s == null)
+ {
+ Log.Warn("The service with id '" + descriptor.Id + "' does not exist. Nothing to uninstall");
+ return; // there's no such service, so consider it already uninstalled
+ }
+
+ if (s.Started)
+ {
+ // We could fail the opeartion here, but it would be an incompatible change.
+ // So it is just a warning
+ Log.Warn("The service with id '" + descriptor.Id + "' is running. It may be impossible to uninstall it");
+ }
+
+ try
+ {
+ s.Delete();
+ }
+ catch (WmiException e)
+ {
+ if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
+ {
+ Log.Error("Failed to uninstall the service with id '" + descriptor.Id + "'"
+ + ". It has been marked for deletion.");
+
+ // TODO: change the default behavior to Error?
+ return; // it's already uninstalled, so consider it a success
}
else
{
- if (descriptor.HasServiceAccount())
- {
- username = descriptor.ServiceAccountUser;
- password = descriptor.ServiceAccountPassword;
- setallowlogonasaserviceright = descriptor.AllowServiceAcountLogonRight;
- }
+ Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'");
}
- if (setallowlogonasaserviceright)
- {
- LogonAsAService.AddLogonAsAServiceRight(username!);
- }
-
- svc.Create(
- descriptor.Id,
- descriptor.Caption,
- "\"" + descriptor.ExecutablePath + "\"",
- ServiceType.OwnProcess,
- ErrorControl.UserNotified,
- descriptor.StartMode,
- descriptor.Interactive,
- username,
- password,
- descriptor.ServiceDependencies);
-
- // update the description
- /* Somehow this doesn't work, even though it doesn't report an error
- Win32Service s = svc.Select(d.Id);
- s.Description = d.Description;
- s.Commit();
- */
-
- // so using a classic method to set the description. Ugly.
- Registry.LocalMachine
- .OpenSubKey("System")
- .OpenSubKey("CurrentControlSet")
- .OpenSubKey("Services")
- .OpenSubKey(descriptor.Id, true)
- .SetValue("Description", descriptor.Description);
-
- var actions = descriptor.FailureActions;
- var isDelayedAutoStart = descriptor.StartMode == StartMode.Automatic && descriptor.DelayedAutoStart;
- if (actions.Count > 0 || isDelayedAutoStart)
- {
- using ServiceManager scm = new ServiceManager();
- using Service sc = scm.Open(descriptor.Id);
-
- // Delayed auto start
- if (isDelayedAutoStart)
- {
- sc.SetDelayedAutoStart(true);
- }
-
- // Set the failure actions
- if (actions.Count > 0)
- {
- sc.ChangeConfig(descriptor.ResetFailureAfter, actions);
- }
- }
-
- return;
+ throw e;
}
- if (args[0] == "uninstall")
- {
- Log.Info("Uninstalling the service with id '" + descriptor.Id + "'");
- if (s == null)
- {
- Log.Warn("The service with id '" + descriptor.Id + "' does not exist. Nothing to uninstall");
- return; // there's no such service, so consider it already uninstalled
- }
+ return;
+ }
- if (s.Started)
- {
- // We could fail the opeartion here, but it would be an incompatible change.
- // So it is just a warning
- Log.Warn("The service with id '" + descriptor.Id + "' is running. It may be impossible to uninstall it");
- }
+ if (args[0] == "start")
+ {
+ Log.Info("Starting the service with id '" + descriptor.Id + "'");
+ if (s == null)
+ ThrowNoSuchService();
- try
- {
- s.Delete();
- }
- catch (WmiException e)
- {
- if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
- {
- Log.Error("Failed to uninstall the service with id '" + descriptor.Id + "'"
- + ". It has been marked for deletion.");
+ s.StartService();
+ return;
+ }
- // TODO: change the default behavior to Error?
- return; // it's already uninstalled, so consider it a success
- }
- else
- {
- Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. WMI Error code is '" + e.ErrorCode + "'");
- }
+ if (args[0] == "stop")
+ {
+ Log.Info("Stopping the service with id '" + descriptor.Id + "'");
+ if (s == null)
+ ThrowNoSuchService();
- throw e;
- }
+ s.StopService();
+ return;
+ }
- return;
- }
-
- if (args[0] == "start")
- {
- Log.Info("Starting the service with id '" + descriptor.Id + "'");
- if (s == null)
- ThrowNoSuchService();
-
- s.StartService();
- return;
- }
-
- if (args[0] == "stop")
- {
- Log.Info("Stopping the service with id '" + descriptor.Id + "'");
- if (s == null)
- ThrowNoSuchService();
+ if (args[0] == "restart")
+ {
+ Log.Info("Restarting the service with id '" + descriptor.Id + "'");
+ if (s == null)
+ ThrowNoSuchService();
+ if (s.Started)
s.StopService();
- return;
- }
- if (args[0] == "restart")
+ while (s.Started)
{
- Log.Info("Restarting the service with id '" + descriptor.Id + "'");
- if (s == null)
- ThrowNoSuchService();
-
- if (s.Started)
- s.StopService();
-
- while (s.Started)
- {
- Thread.Sleep(1000);
- s = svc.Select(descriptor.Id);
- }
-
- s.StartService();
- return;
- }
-
- if (args[0] == "restart!")
- {
- Log.Info("Restarting the service with id '" + descriptor.Id + "'");
-
- // run restart from another process group. see README.md for why this is useful.
-
- STARTUPINFO si = default;
- bool result = Kernel32.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, 0x200/*CREATE_NEW_PROCESS_GROUP*/, IntPtr.Zero, null, ref si, out _);
- if (!result)
- {
- throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
- }
-
- return;
- }
-
- if (args[0] == "status")
- {
- Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'");
- if (s == null)
- Console.WriteLine("NonExistent");
- else if (s.Started)
- Console.WriteLine("Started");
- else
- Console.WriteLine("Stopped");
-
- return;
- }
-
- if (args[0] == "test")
- {
- WrapperService wsvc = new WrapperService(descriptor);
- wsvc.OnStart(args.ToArray());
Thread.Sleep(1000);
- wsvc.OnStop();
- return;
+ s = svc.Select(descriptor.Id);
}
- if (args[0] == "testwait")
- {
- WrapperService wsvc = new WrapperService(descriptor);
- wsvc.OnStart(args.ToArray());
- Console.WriteLine("Press any key to stop the service...");
- Console.Read();
- wsvc.OnStop();
- return;
- }
-
- if (args[0] == "help" || args[0] == "--help" || args[0] == "-h"
- || args[0] == "-?" || args[0] == "/?")
- {
- printHelp();
- return;
- }
-
- if (args[0] == "version")
- {
- printVersion();
- return;
- }
-
- Console.WriteLine("Unknown command: " + args[0]);
- printAvailableCommandsInfo();
- throw new Exception("Unknown command: " + args[0]);
+ s.StartService();
+ return;
}
- else
+
+ if (args[0] == "restart!")
{
- Log.Info("Starting ServiceWrapper in the service mode");
+ Log.Info("Restarting the service with id '" + descriptor.Id + "'");
+
+ // run restart from another process group. see README.md for why this is useful.
+
+ STARTUPINFO si = default;
+ bool result = Kernel32.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, 0x200/*CREATE_NEW_PROCESS_GROUP*/, IntPtr.Zero, null, ref si, out _);
+ if (!result)
+ {
+ throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
+ }
+
+ return;
}
- Run(new WrapperService(descriptor));
+ if (args[0] == "status")
+ {
+ Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'");
+ if (s == null)
+ Console.WriteLine("NonExistent");
+ else if (s.Started)
+ Console.WriteLine("Started");
+ else
+ Console.WriteLine("Stopped");
+
+ return;
+ }
+
+ if (args[0] == "test")
+ {
+ WrapperService wsvc = new WrapperService(descriptor);
+ wsvc.OnStart(args.ToArray());
+ Thread.Sleep(1000);
+ wsvc.OnStop();
+ return;
+ }
+
+ if (args[0] == "testwait")
+ {
+ WrapperService wsvc = new WrapperService(descriptor);
+ wsvc.OnStart(args.ToArray());
+ Console.WriteLine("Press any key to stop the service...");
+ Console.Read();
+ wsvc.OnStop();
+ return;
+ }
+
+ if (args[0] == "help" || args[0] == "--help" || args[0] == "-h"
+ || args[0] == "-?" || args[0] == "/?")
+ {
+ printHelp();
+ return;
+ }
+
+ if (args[0] == "version")
+ {
+ printVersion();
+ return;
+ }
+
+ Console.WriteLine("Unknown command: " + args[0]);
+ printAvailableCommandsInfo();
+ throw new Exception("Unknown command: " + args[0]);
}
private static void InitLoggers(ServiceDescriptor d, bool enableCLILogging)
From ff60522b0d6d0889debde2a52e5c1264de6d2527 Mon Sep 17 00:00:00 2001
From: NextTurn <45985406+NextTurn@users.noreply.github.com>
Date: Thu, 31 Oct 2019 00:00:00 +0800
Subject: [PATCH 2/2] Use Debug logging level for modes
---
src/Core/ServiceWrapper/Main.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Core/ServiceWrapper/Main.cs b/src/Core/ServiceWrapper/Main.cs
index 26e2bb5..c670dd5 100644
--- a/src/Core/ServiceWrapper/Main.cs
+++ b/src/Core/ServiceWrapper/Main.cs
@@ -496,7 +496,7 @@ namespace winsw
if (!inCliMode)
{
- Log.Info("Starting WinSW in the service mode");
+ Log.Debug("Starting WinSW in the service mode");
Run(new WrapperService(descriptor));
return;
}