diff --git a/NewLife.Agent/IHost.cs b/NewLife.Agent/IHost.cs index 19579ce..e6674fd 100644 --- a/NewLife.Agent/IHost.cs +++ b/NewLife.Agent/IHost.cs @@ -40,6 +40,14 @@ public interface IHost /// 服务名 Boolean Restart(String serviceName); + ///// 安装为自启动。登录进入桌面后启动 + ///// 服务名 + ///// 显示名 + ///// 文件路径 + ///// 描述信息 + ///// + //Boolean InstallAutorun(String serviceName, String displayName, String binPath, String description); + /// 开始执行服务 /// void Run(ServiceBase service); @@ -99,6 +107,14 @@ public abstract class Host : DisposeBase, IHost /// 服务名 public virtual Boolean Restart(String serviceName) => false; + ///// 安装为自启动。登录进入桌面后启动 + ///// 服务名 + ///// 显示名 + ///// 文件路径 + ///// 描述信息 + ///// + //public virtual Boolean InstallAutorun(String serviceName, String displayName, String binPath, String description) => false; + /// 开始执行服务 /// public abstract void Run(ServiceBase service); diff --git a/NewLife.Agent/ServiceBase.cs b/NewLife.Agent/ServiceBase.cs index 39f398a..034e7da 100644 --- a/NewLife.Agent/ServiceBase.cs +++ b/NewLife.Agent/ServiceBase.cs @@ -26,6 +26,9 @@ public abstract class ServiceBase : DisposeBase /// 描述 public String Description { get; set; } + + /// 是否使用自启动。自启动需要用户登录桌面,默认false使用系统服务 + public Boolean UseAutorun { get; set; } #endregion #region 构造 @@ -109,12 +112,18 @@ protected virtual void Init() if (Host == null) { if (Runtime.Windows) - Host = new WindowsService { Service = this }; + { + if (UseAutorun) + Host = new WindowsAutorun { Service = this }; + else + Host = new WindowsService { Service = this }; + } else if (Systemd.Available) Host = new Systemd { Service = this }; - else Host = RcInit.Available ? - (IHost)new RcInit { Service = this } : - throw new NotSupportedException($"不支持该操作系统!"); + else if (RcInit.Available) + Host = new RcInit { Service = this }; + else + throw new NotSupportedException($"不支持该操作系统!"); } Log = XTrace.Log; @@ -259,6 +268,9 @@ protected virtual void ProcessMenu() } #endregion break; + //case '6': + // InstallAutorun(); + // break; case '7': if (WatchDogs.Length > 0) CheckWatchDog(); break; @@ -312,6 +324,11 @@ protected virtual void ShowMenu() Console.WriteLine("5 模拟运行 -run"); } + //if (Runtime.Windows) + //{ + // Console.WriteLine("6 安装开机自启 -autorun"); + //} + var dogs = WatchDogs; if (dogs.Length > 0) { @@ -594,7 +611,7 @@ private void Install() exe = args[0].GetFullPath(); } - var bin = $"{exe} -s"; + var bin = UseAutorun ? $"{exe} -run" : $"{exe} -s"; // 兼容更多参数做为服务启动,譬如:--urls if (args.Length > 2) diff --git a/NewLife.Agent/WindowsAutorun.cs b/NewLife.Agent/WindowsAutorun.cs new file mode 100644 index 0000000..ea167b1 --- /dev/null +++ b/NewLife.Agent/WindowsAutorun.cs @@ -0,0 +1,212 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security.Principal; +using System.Text; +using Microsoft.Win32; +using NewLife.Log; + +namespace NewLife.Agent; + +/// Windows自启动 +/// +/// 桌面登录自启动 +/// +public class WindowsAutorun : Host +{ + /// 开始执行服务 + /// + public override void Run(ServiceBase service) => throw new NotSupportedException(); + + /// 服务是否已安装 + /// 服务名 + /// +#if NET5_0_OR_GREATER + [SupportedOSPlatform("windows")] +#endif + public override Boolean IsInstalled(String serviceName) + { +#if NET40_OR_GREATER || NET5_0_OR_GREATER + // 在注册表中写入启动配置 + using var key = Registry.LocalMachine.OpenSubKey("""SOFTWARE\Microsoft\Windows\CurrentVersion\Run""", false); + + var v = key.GetValue(serviceName)?.ToString(); + return !v.IsNullOrEmpty(); +#else + return false; +#endif + } + + /// 服务是否已启动 + /// 服务名 + /// + public override unsafe Boolean IsRunning(String serviceName) => false; + + /// 安装服务 + /// 服务名 + /// + /// + /// + /// +#if NET5_0_OR_GREATER + [SupportedOSPlatform("windows")] +#endif + public override Boolean Install(String serviceName, String displayName, String binPath, String description) + { + XTrace.WriteLine("{0}.Install {1}, {2}, {3}, {4}", GetType().Name, serviceName, displayName, binPath, description); + +#if !NETSTANDARD + if (!IsAdministrator()) return RunAsAdministrator("-i"); +#endif + + //// 当前用户的启动目录 + //var dir = Environment.GetFolderPath(Environment.SpecialFolder.Startup); + //if (dir.IsNullOrEmpty()) return false; + + //dir.EnsureDirectory(false); + +#if NET40_OR_GREATER || NET5_0_OR_GREATER + // 在注册表中写入启动配置 + using var key = Registry.LocalMachine.OpenSubKey("""SOFTWARE\Microsoft\Windows\CurrentVersion\Run""", true); + + // 添加应用程序到自启动项 + key.SetValue(serviceName, binPath); +#endif + + return true; + } + + /// 卸载服务 + /// 服务名 + /// +#if NET5_0_OR_GREATER + [SupportedOSPlatform("windows")] +#endif + public override unsafe Boolean Remove(String serviceName) + { + XTrace.WriteLine("{0}.Remove {1}", GetType().Name, serviceName); + +#if !NETSTANDARD + if (!IsAdministrator()) return RunAsAdministrator("-uninstall"); +#endif + +#if NET40_OR_GREATER || NET5_0_OR_GREATER + // 在注册表中写入启动配置 + using var key = Registry.LocalMachine.OpenSubKey("""SOFTWARE\Microsoft\Windows\CurrentVersion\Run""", true); + key.DeleteValue(serviceName, false); +#endif + + return true; + } + + /// 查询服务配置 + /// 服务名 +#if NET5_0_OR_GREATER + [SupportedOSPlatform("windows")] +#endif + public override unsafe ServiceConfig QueryConfig(String serviceName) + { +#if NET40_OR_GREATER || NET5_0_OR_GREATER + using var key = Registry.LocalMachine.OpenSubKey("""SOFTWARE\Microsoft\Windows\CurrentVersion\Run""", false); + + var v = key.GetValue(serviceName)?.ToString(); + if (v.IsNullOrEmpty()) return null; + + return new ServiceConfig + { + Name = serviceName, + FilePath = v.TrimEnd(" -run") + }; +#else + return null; +#endif + } + +#if !NETSTANDARD + static Boolean RunAsAdministrator(String argument) + { + var exe = ExecutablePath; + if (exe.IsNullOrEmpty()) return false; + + if (exe.EndsWithIgnoreCase(".dll")) + { + var exe2 = Path.ChangeExtension(exe, ".exe"); + if (File.Exists(exe2)) exe = exe2; + } + + var startInfo = exe.EndsWithIgnoreCase(".dll") ? + new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"{Path.GetFileName(exe)} {argument}", + WorkingDirectory = Path.GetDirectoryName(exe), + Verb = "runas", + UseShellExecute = true, + } : + new ProcessStartInfo + { + FileName = exe, + Arguments = argument, + Verb = "runas", + UseShellExecute = true, + }; + + var p = Process.Start(startInfo); + return !p.WaitForExit(5_000) || p.ExitCode == 0; + } + + static String _executablePath; + static String ExecutablePath + { + get + { + if (_executablePath == null) + { + var entryAssembly = Assembly.GetEntryAssembly(); + if (entryAssembly != null) + { + var codeBase = entryAssembly.CodeBase; + var uri = new Uri(codeBase); + _executablePath = uri.IsFile ? uri.LocalPath + Uri.UnescapeDataString(uri.Fragment) : uri.ToString(); + } + else + { + var moduleFileNameLongPath = GetModuleFileNameLongPath(new HandleRef(null, IntPtr.Zero)); + _executablePath = moduleFileNameLongPath.ToString().GetFullPath(); + } + } + + return _executablePath; + } + } + + static StringBuilder GetModuleFileNameLongPath(HandleRef hModule) + { + var sb = new StringBuilder(260); + var num = 1; + var num2 = 0; + while ((num2 = GetModuleFileName(hModule, sb, sb.Capacity)) == sb.Capacity && Marshal.GetLastWin32Error() == 122 && sb.Capacity < 32767) + { + num += 2; + var capacity = (num * 260 < 32767) ? (num * 260) : 32767; + sb.EnsureCapacity(capacity); + } + sb.Length = num2; + return sb; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern Int32 GetModuleFileName(HandleRef hModule, StringBuilder buffer, Int32 length); + +#if NET5_0_OR_GREATER + [SupportedOSPlatform("windows")] +#endif + private Boolean IsAdministrator() + { + var current = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(current); + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } +#endif +} \ No newline at end of file diff --git a/Test/MyServices.cs b/Test/MyServices.cs index 86dfe8a..9ad0ca1 100644 --- a/Test/MyServices.cs +++ b/Test/MyServices.cs @@ -20,6 +20,8 @@ public MyServices() DisplayName = "新生命服务代理"; Description = "用于承载各种服务的服务代理!"; + + UseAutorun = true; } #endregion