You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让.NET 8应用在控制台运行时自行安装为Windows服务?

可行方案推荐

方案一:基于Windows原生API的自实现(稳定可靠,无外部依赖)

核心思路是直接调用Windows Advapi32.dll中的服务管理API完成安装/卸载逻辑,同时结合Microsoft.Extensions.Hosting.WindowsServices实现控制台/服务双模式运行,完全规避第三方工具或PowerShell版本兼容问题。

步骤1:集成Windows服务托管

在现有控制台项目中安装Microsoft.Extensions.Hosting.WindowsServices包,修改Program.cs实现双模式启动:

var builder = Host.CreateDefaultBuilder(args);

// 判断是否以服务模式运行
if (WindowsServiceHelpers.IsWindowsService())
{
    builder.UseWindowsService();
}

// 注册你的业务服务(继承BackgroundService的自定义类)
builder.ConfigureServices(services =>
{
    services.AddHostedService<YourWorkerService>();
});

var host = builder.Build();

// 处理安装/卸载命令行参数
var command = args.FirstOrDefault()?.ToLower();
switch (command)
{
    case "install":
        ServiceInstaller.Install("YourServiceID", "你的服务显示名称", "服务功能描述", Environment.ProcessPath);
        Console.WriteLine("服务安装成功");
        return;
    case "uninstall":
        ServiceInstaller.Uninstall("YourServiceID");
        Console.WriteLine("服务卸载成功");
        return;
    default:
        // 正常启动(控制台或服务模式)
        await host.RunAsync();
        break;
}

步骤2:封装服务管理的原生API

通过P/Invoke调用Windows系统API,实现稳定的服务安装/卸载逻辑:

using System.Runtime.InteropServices;
using System.Security.Principal;

public static class ServiceInstaller
{
    private const int SERVICE_WIN32_OWN_PROCESS = 0x10;
    private const int SERVICE_AUTO_START = 0x2;
    private const int SERVICE_ERROR_NORMAL = 0x1;

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, uint dwDesiredAccess);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateService(IntPtr hSCManager, string lpServiceName, string lpDisplayName, uint dwDesiredAccess, uint dwServiceType, uint dwStartType, uint dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, IntPtr lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool DeleteService(IntPtr hService);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool CloseServiceHandle(IntPtr hSCObject);

    private const uint SC_MANAGER_CREATE_SERVICE = 0x2;
    private const uint SERVICE_ALL_ACCESS = 0xF01FF;

    public static void Install(string serviceId, string displayName, string description, string exePath)
    {
        if (!IsAdmin())
            throw new InvalidOperationException("安装服务需要管理员权限");

        IntPtr scManager = OpenSCManager(null, null, SC_MANAGER_CREATE_SERVICE);
        if (scManager == IntPtr.Zero)
            throw new System.ComponentModel.Win32Exception();

        try
        {
            IntPtr service = CreateService(
                scManager,
                serviceId,
                displayName,
                SERVICE_ALL_ACCESS,
                SERVICE_WIN32_OWN_PROCESS,
                SERVICE_AUTO_START,
                SERVICE_ERROR_NORMAL,
                exePath,
                null,
                IntPtr.Zero,
                null,
                null,
                null);

            if (service == IntPtr.Zero)
                throw new System.ComponentModel.Win32Exception();

            SetServiceDescription(service, description);
            CloseServiceHandle(service);
        }
        finally
        {
            CloseServiceHandle(scManager);
        }
    }

    public static void Uninstall(string serviceId)
    {
        if (!IsAdmin())
            throw new InvalidOperationException("卸载服务需要管理员权限");

        IntPtr scManager = OpenSCManager(null, null, SERVICE_ALL_ACCESS);
        if (scManager == IntPtr.Zero)
            throw new System.ComponentModel.Win32Exception();

        try
        {
            IntPtr service = OpenService(scManager, serviceId, SERVICE_ALL_ACCESS);
            if (service == IntPtr.Zero)
                throw new System.ComponentModel.Win32Exception();

            try
            {
                if (!DeleteService(service))
                    throw new System.ComponentModel.Win32Exception();
            }
            finally
            {
                CloseServiceHandle(service);
            }
        }
        finally
        {
            CloseServiceHandle(scManager);
        }
    }

    private static bool IsAdmin()
    {
        using var identity = WindowsIdentity.GetCurrent();
        var principal = new WindowsPrincipal(identity);
        return principal.IsInRole(WindowsBuiltInRole.Administrator);
    }

    // 封装服务描述设置API
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct SERVICE_DESCRIPTION
    {
        public string lpDescription;
    }

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool ChangeServiceConfig2(IntPtr hService, uint dwInfoLevel, IntPtr lpInfo);

    private const uint SERVICE_CONFIG_DESCRIPTION = 0x1;

    private static void SetServiceDescription(IntPtr hService, string description)
    {
        var serviceDesc = new SERVICE_DESCRIPTION { lpDescription = description };
        IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(serviceDesc));
        Marshal.StructureToPtr(serviceDesc, ptr, false);

        try
        {
            if (!ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, ptr))
                throw new System.ComponentModel.Win32Exception();
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

方案优势

  • 直接依赖Windows系统原生API,稳定性不受第三方工具或PowerShell版本更新影响
  • 完全进程内实现,适配自定义安装流程
  • 可灵活配置服务启动类型、权限、描述等所有参数

方案二:使用维护活跃的TopShelf替代库

如果不想编写底层API逻辑,可选用Topshelf.Extensions.Hosting——这是基于.NET Generic Host的TopShelf替代方案,支持.NET 6+,目前仍在维护,能实现和TopShelf一致的命令行安装/卸载体验。

步骤1:安装NuGet包

Install-Package Topshelf.Extensions.Hosting

步骤2:修改Program.cs

var builder = Host.CreateDefaultBuilder(args);

builder.ConfigureServices(services =>
{
    services.AddHostedService<YourWorkerService>();
});

var app = builder.Build();
await app.RunAsServiceAsync(configure =>
{
    configure.ServiceName("YourServiceID");
    configure.DisplayName("你的服务显示名称");
    configure.Description("服务功能描述");
    configure.StartAutomatically();

    // 自动支持 install/uninstall/start/stop 等命令行参数
});

方案优势

  • 用法与TopShelf高度一致,学习成本低
  • 基于现代.NET Generic Host架构,兼容性好
  • 项目维护活跃,无停更风险

方案三:轻量外部工具辅助(备选)

若暂时不想修改核心代码,可使用NSSM(Non-Sucking Service Manager)——一款稳定的开源工具,能将任意控制台程序注册为Windows服务,支持命令行操作。可将NSSM打包进你的安装包,通过程序调用其命令完成服务配置。

示例命令

# 安装服务
nssm install YourServiceID "path\to\your\app.exe"
# 设置服务描述
nssm set YourServiceID Description "服务功能描述"
# 卸载服务
nssm remove YourServiceID confirm

方案优势

  • 无需编写服务安装的底层逻辑,快速落地需求
  • NSSM功能完善,支持服务的所有配置项,稳定性经过长期验证

内容的提问来源于stack exchange,提问作者Alberto Chiesa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:25:24