更新到最新dotnet运行时版本
大石头 authored at 2024-08-29 18:23:57
10.66 KiB
LuckyClover
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using NewLife;
using NewLife.Log;
using static Installer.Advapi32;

namespace Installer;

public class WindowsService
{
    #region 服务状态和控制
    /// <summary>服务是否已安装</summary>
    /// <param name="serviceName">服务名</param>
    /// <returns></returns>
    public Boolean IsInstalled(String serviceName)
    {
        using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CONNECT));
        if (manager == null || manager.IsInvalid) return false;

        using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_QUERY_CONFIG));
        return service != null && !service.IsInvalid;
    }

    /// <summary>服务是否已启动</summary>
    /// <param name="serviceName">服务名</param>
    /// <returns></returns>
    public unsafe Boolean IsRunning(String serviceName)
    {
        using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CONNECT));
        if (manager == null || manager.IsInvalid) return false;

        using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_QUERY_STATUS));
        if (service == null || service.IsInvalid) return false;

        SERVICE_STATUS status = default;
        return !QueryServiceStatus(service, &status)
            ? throw new Win32Exception(Marshal.GetLastWin32Error())
            : status.currentState == ServiceControllerStatus.Running;
    }

    /// <summary>安装服务</summary>
    /// <param name="serviceName">服务名</param>
    /// <param name="displayName"></param>
    /// <param name="binPath"></param>
    /// <param name="description"></param>
    /// <returns></returns>
    public Boolean Install(String serviceName, String displayName, String binPath, String description)
    {
        XTrace.WriteLine("{0}.Install {1}, {2}, {3}, {4}", GetType().Name, serviceName, displayName, binPath, description);

        using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CREATE_SERVICE));
        if (manager.IsInvalid)
        {
            XTrace.WriteLine("安装Windows服务要求以管理员运行");
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        using var service = new SafeServiceHandle(CreateService(manager, serviceName, displayName, ServiceOptions.SERVICE_ALL_ACCESS, 0x10, 2, 1, binPath, null, 0, null, null, null));
        if (service.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());

        // 设置描述信息
        if (!description.IsNullOrEmpty())
        {
            SERVICE_DESCRIPTION sd;
            sd.Description = Marshal.StringToHGlobalUni(description);
            var lpInfo = Marshal.AllocHGlobal(Marshal.SizeOf(sd));

            try
            {
                Marshal.StructureToPtr(sd, lpInfo, false);

                const Int32 SERVICE_CONFIG_DESCRIPTION = 1;
                ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, lpInfo);
            }
            finally
            {
                Marshal.FreeHGlobal(lpInfo);
                Marshal.FreeHGlobal(sd.Description);
            }
        }

        return true;
    }

    /// <summary>卸载服务</summary>
    /// <param name="serviceName">服务名</param>
    /// <returns></returns>
    public unsafe Boolean Remove(String serviceName)
    {
        XTrace.WriteLine("{0}.Remove {1}", GetType().Name, serviceName);

        if (!IsAdministrator()) return RunAsAdministrator("-uninstall");

        using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_ALL));
        if (manager.IsInvalid)
        {
            XTrace.WriteLine("卸载Windows服务要求以管理员运行");
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_STOP | ServiceOptions.STANDARD_RIGHTS_DELETE));
        if (service.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());

        SERVICE_STATUS status = default;
        ControlService(service, ControlOptions.Stop, &status);

        return DeleteService(service) == 0 ? throw new Win32Exception(Marshal.GetLastWin32Error()) : true;
    }

    /// <summary>启动服务</summary>
    /// <param name="serviceName">服务名</param>
    /// <returns></returns>
    public Boolean Start(String serviceName)
    {
        XTrace.WriteLine("{0}.Start {1}", GetType().Name, serviceName);

        if (!IsAdministrator()) return RunAsAdministrator("-start");

        using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_CONNECT));
        if (manager.IsInvalid)
        {
            XTrace.WriteLine("启动Windows服务要求以管理员运行");
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_START));
        return service.IsInvalid
            ? throw new Win32Exception(Marshal.GetLastWin32Error())
            : !StartService(service, 0, IntPtr.Zero) ? throw new Win32Exception(Marshal.GetLastWin32Error()) : true;
    }

    /// <summary>停止服务</summary>
    /// <param name="serviceName">服务名</param>
    /// <returns></returns>
    public unsafe Boolean Stop(String serviceName)
    {
        XTrace.WriteLine("{0}.Stop {1}", GetType().Name, serviceName);

        if (!IsAdministrator()) return RunAsAdministrator("-stop");

        using var manager = new SafeServiceHandle(OpenSCManager(null, null, ServiceControllerOptions.SC_MANAGER_ALL));
        if (manager.IsInvalid)
        {
            XTrace.WriteLine("停止Windows服务要求以管理员运行");
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        using var service = new SafeServiceHandle(OpenService(manager, serviceName, ServiceOptions.SERVICE_STOP));
        if (service.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());

        SERVICE_STATUS status = default;
        return !ControlService(service, ControlOptions.Stop, &status) ? throw new Win32Exception(Marshal.GetLastWin32Error()) : true;
    }

    /// <summary>重启服务</summary>
    /// <param name="serviceName">服务名</param>
    public Boolean Restart(String serviceName)
    {
        XTrace.WriteLine("{0}.Restart {1}", GetType().Name, serviceName);

        if (!IsAdministrator()) return RunAsAdministrator("-restart");

        //if (InService)
        {
            var cmd = $"/c net stop {serviceName} & ping 127.0.0.1 -n 5 & net start {serviceName}";
            Process.Start("cmd.exe", cmd);
        }
        //else
        //{
        //    Process.Start(Service.GetExeName(), "-run -delay");
        //}

        //// 在临时目录生成重启服务的批处理文件
        //var filename = "重启.bat".GetFullPath();
        //if (File.Exists(filename)) File.Delete(filename);

        //File.AppendAllText(filename, "net stop " + serviceName);
        //File.AppendAllText(filename, Environment.NewLine);
        //File.AppendAllText(filename, "ping 127.0.0.1 -n 5 > nul ");
        //File.AppendAllText(filename, Environment.NewLine);
        //File.AppendAllText(filename, "net start " + serviceName);

        ////执行重启服务的批处理
        ////RunCmd(filename, false, false);
        //var p = new Process();
        //var si = new ProcessStartInfo
        //{
        //    FileName = filename,
        //    UseShellExecute = true,
        //    CreateNoWindow = true
        //};
        //p.StartInfo = si;

        //p.Start();

        ////if (File.Exists(filename)) File.Delete(filename);

        return true;
    }

    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);

    public Boolean IsAdministrator()
    {
        var current = WindowsIdentity.GetCurrent();
        var windowsPrincipal = new WindowsPrincipal(current);
        return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
    }
    #endregion
}