本文演示如何在 Visual Studio 中创建 .NET Framework Windows 服务应用。 服务只需将消息写入事件日志。
注释
本文不适用于 .NET 中的托管服务。 有关使用 Microsoft.Extensions.Hosting.BackgroundService 和工作服务模板的 Windows 服务的最新内容,请参阅:
- .NET 中的工作服务
使用 创建 Windows 服务
创建服务
若要开始,请创建项目并设置服务正常运行所需的值。
在 Visual Studio 文件 菜单中,选择“ 新建>项目 ”(或按 Ctrl+Shift+N)打开 “新建项目 ”窗口。
查找并选择 Windows 服务(.NET Framework) 项目模板。
注释
如果未看到 Windows 服务 模板,可能需要使用 Visual Studio Installer 安装 .NET 桌面开发 工作负载。
对于 项目名称,请输入 MyNewService,然后选择“ 创建”。
将显示 “设计 ”选项卡(Service1.cs[设计] 或 Service1.vb[设计])。
项目模板包括一个名为
Service1并继承自System.ServiceProcess.ServiceBase的组件类。 它包括许多基本服务代码,例如启动服务的代码。
重命名服务
将服务从 Service1 重命名为 MyNewService。
在 解决方案资源管理器中,选择 Service1.cs 或 Service1.vb,然后从快捷菜单中选择“ 重命名 ”。 将文件重命名为 MyNewService.cs 或 MyNewService.vb,然后按 Enter。
此时会显示一个弹出窗口,询问是否要重命名对代码元素 Service1 的所有引用。
在弹出窗口中,选择“ 是”。
从“文件”菜单中选择“全部保存”。
向服务添加功能
在本部分中,将自定义事件日志添加到 Windows 服务。 该 EventLog 组件是可以添加到 Windows 服务的组件类型的示例。
添加自定义事件日志功能
在 “工具箱” 窗口中,展开 “组件”,然后将 EventLog 组件拖到 Service1.cs [设计] 或 Service1.vb [设计] 设计器。
小窍门
如果未看到“工具箱”窗口,请选择“ 视图>工具箱”。
在 解决方案资源管理器中,从 MyNewService.cs 或 MyNewService.vb的快捷菜单中,选择 “查看代码”。
定义自定义事件日志。
对于 C#,请编辑现有
MyNewService()构造函数,如以下代码片段所示。 对于 Visual Basic,请添加New()构造函数,如以下代码片段所示。public MyNewService() { InitializeComponent(); eventLog1 = new EventLog(); if (!EventLog.SourceExists("MySource")) { EventLog.CreateEventSource("MySource", "MyNewLog"); } eventLog1.Source = "MySource"; eventLog1.Log = "MyNewLog"; }' To access the constructor in Visual Basic, select New from the ' method name drop-down list. Public Sub New() MyBase.New() InitializeComponent() Me.EventLog1 = New System.Diagnostics.EventLog If Not System.Diagnostics.EventLog.SourceExists("MySource") Then System.Diagnostics.EventLog.CreateEventSource("MySource", "MyNewLog") End If EventLog1.Source = "MySource" EventLog1.Log = "MyNewLog" End Sub如果尚不存在,请为命名空间添加
using指令到MyNewService.cs,或添加Imports语句到MyNewService.vb。using System.Diagnostics;Imports System.Diagnostics从“文件”菜单中选择“全部保存”。
定义服务启动时发生的情况
在 MyNewService.cs 或 MyNewService.vb的代码编辑器中,找到该方法 OnStart 。 创建项目时,Visual Studio 会自动创建一个空的方法定义。 添加在服务启动时将条目写入事件日志的代码:
protected override void OnStart(string[] args)
{
eventLog1.WriteEntry("In OnStart.");
}
' To access the OnStart in Visual Basic, select OnStart from the
' method name drop-down list.
Protected Overrides Sub OnStart(ByVal args() As String)
EventLog1.WriteEntry("In OnStart")
End Sub
投票
由于服务应用程序被设计为长时间运行,因此通常会轮询或监控在 OnStart 方法中设置的系统。 该方法 OnStart 必须在服务开始运行后返回到操作系统,以便系统不会被阻止。
若要设置简单的轮询机制,请使用组件 System.Timers.Timer 。 计时器会定期引发 Elapsed 事件,此时服务可以执行监控操作。 使用 Timer 组件,如下所示:
设置轮询机制
为了命名空间,在 MyNewService.cs 中添加
using指令,或者在 MyNewService.vb 中添加Imports语句:using System.Timers;Imports System.Timers在
MyNewService.OnStart事件中添加以下代码以设置轮询机制:// Set up a timer that triggers every minute. Timer timer = new Timer { Interval = 60000 // 60 seconds }; timer.Elapsed += new ElapsedEventHandler(this.OnTimer); timer.Start();' Set up a timer that triggers every minute. Dim timer As Timer = New Timer() timer.Interval = 60000 ' 60 seconds AddHandler timer.Elapsed, AddressOf Me.OnTimer timer.Start()在类中添加
MyNewService成员变量。 它包含要写入事件日志的下一个事件的标识符:private int eventId = 1;Private eventId As Integer = 1在类中
MyNewService,添加OnTimer用于处理 Timer.Elapsed 事件的方法:public void OnTimer(object sender, ElapsedEventArgs args) { // TODO: Insert monitoring activities here. eventLog1.WriteEntry("Monitoring the System", EventLogEntryType.Information, eventId++); }Private Sub OnTimer(sender As Object, e As Timers.ElapsedEventArgs) ' TODO: Insert monitoring activities here. eventLog1.WriteEntry("Monitoring the System", EventLogEntryType.Information, eventId) eventId = eventId + 1 End Sub
可以使用后台工作线程运行任务,而不是在主线程上运行所有工作。 有关详细信息,请参阅 System.ComponentModel.BackgroundWorker。
定义停止服务时发生的情况
在 OnStop 方法中插入一行代码,在停止服务时向事件日志中添加一个条目:
protected override void OnStop()
{
eventLog1.WriteEntry("In OnStop.");
}
Protected Overrides Sub OnStop()
EventLog1.WriteEntry("In OnStop.")
End Sub
定义服务的其他操作
可以重写OnPause、OnContinue和OnShutdown方法,以便为组件定义其他处理。
以下代码演示如何重写 OnContinue 类中的 MyNewService 方法:
protected override void OnContinue()
{
eventLog1.WriteEntry("In OnContinue.");
}
Protected Overrides Sub OnContinue()
EventLog1.WriteEntry("In OnContinue.")
End Sub
设置服务状态
服务将其状态报告给 服务控制管理器 ,以便用户可以判断服务是否正常运行。 默认情况下,从 ServiceBase 继承的服务会报告一组有限的状态设置,其中包括 SERVICE_STOPPED、SERVICE_PAUSED 和 SERVICE_RUNNING。 如果服务需要一段时间才能启动,则报告 SERVICE_START_PENDING 状态非常有用。
可以通过添加调用 Windows SERVICE_START_PENDING 函数的代码来实现SERVICE_STOP_PENDING和状态设置。
为了命名空间,在 MyNewService.cs 中添加
using指令,或者在 MyNewService.vb 中添加Imports语句:using System.Runtime.InteropServices;Imports System.Runtime.InteropServices将以下枚举和结构添加到 MyNewService.cs或 MyNewService.vb,以声明
ServiceState值并为状态添加结构,你将在平台调用中使用:public enum ServiceState { SERVICE_STOPPED = 0x00000001, SERVICE_START_PENDING = 0x00000002, SERVICE_STOP_PENDING = 0x00000003, SERVICE_RUNNING = 0x00000004, SERVICE_CONTINUE_PENDING = 0x00000005, SERVICE_PAUSE_PENDING = 0x00000006, SERVICE_PAUSED = 0x00000007, } [StructLayout(LayoutKind.Sequential)] public struct ServiceStatus { public int dwServiceType; public ServiceState dwCurrentState; public int dwControlsAccepted; public int dwWin32ExitCode; public int dwServiceSpecificExitCode; public int dwCheckPoint; public int dwWaitHint; };Public Enum ServiceState SERVICE_STOPPED = 1 SERVICE_START_PENDING = 2 SERVICE_STOP_PENDING = 3 SERVICE_RUNNING = 4 SERVICE_CONTINUE_PENDING = 5 SERVICE_PAUSE_PENDING = 6 SERVICE_PAUSED = 7 End Enum <StructLayout(LayoutKind.Sequential)> Public Structure ServiceStatus Public dwServiceType As Long Public dwCurrentState As ServiceState Public dwControlsAccepted As Long Public dwWin32ExitCode As Long Public dwServiceSpecificExitCode As Long Public dwCheckPoint As Long Public dwWaitHint As Long End Structure在类中
MyNewService,使用平台调用声明 SetServiceStatus 函数:[DllImport("advapi32.dll", SetLastError = true)] private static extern bool SetServiceStatus(System.IntPtr handle, ref ServiceStatus serviceStatus);Declare Auto Function SetServiceStatus Lib "advapi32.dll" (ByVal handle As IntPtr, ByRef serviceStatus As ServiceStatus) As Boolean若要实现
SERVICE_START_PENDING状态,请将以下代码添加到方法的 OnStart 开头:// Update the service state to Start Pending. ServiceStatus serviceStatus = new ServiceStatus { dwCurrentState = ServiceState.SERVICE_START_PENDING, dwWaitHint = 100000 }; SetServiceStatus(this.ServiceHandle, ref serviceStatus);' Update the service state to Start Pending. Dim serviceStatus As ServiceStatus = New ServiceStatus() serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING serviceStatus.dwWaitHint = 100000 SetServiceStatus(Me.ServiceHandle, serviceStatus)将代码添加到方法末尾
OnStart,将状态设置为SERVICE_RUNNING:// Update the service state to Running. serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING; SetServiceStatus(this.ServiceHandle, ref serviceStatus);' Update the service state to Running. serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING SetServiceStatus(Me.ServiceHandle, serviceStatus)(可选)如果是 OnStop 长时间运行的方法,则在
OnStop该方法中重复此过程。 在方法退出前SERVICE_STOP_PENDING实现状态并返回SERVICE_STOPPED状态OnStop。例如:
// Update the service state to Stop Pending. ServiceStatus serviceStatus = new ServiceStatus { dwCurrentState = ServiceState.SERVICE_STOP_PENDING, dwWaitHint = 100000 }; SetServiceStatus(this.ServiceHandle, ref serviceStatus); // Update the service state to Stopped. serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED; SetServiceStatus(this.ServiceHandle, ref serviceStatus);' Update the service state to Stop Pending. Dim serviceStatus As ServiceStatus = New ServiceStatus() serviceStatus.dwCurrentState = ServiceState.SERVICE_STOP_PENDING serviceStatus.dwWaitHint = 100000 SetServiceStatus(Me.ServiceHandle, serviceStatus) ' Update the service state to Stopped. serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED SetServiceStatus(Me.ServiceHandle, serviceStatus)
将安装程序添加到服务
在运行 Windows 服务之前,需要安装它,它将注册到服务控制管理器。 将安装程序添加到项目以处理注册详细信息。
在 解决方案资源管理器中,从 MyNewService.cs 或 MyNewService.vb的快捷菜单中,选择 “视图设计器”。
在 “设计 ”视图中,选择背景区域,然后从快捷菜单中选择 “添加安装程序 ”。
默认情况下,Visual Studio 会向项目添加一个名为“包含两个安装程序”的
ProjectInstaller组件类。 这些安装程序适用于你的服务和服务的关联进程。在 ProjectInstaller的设计视图中,为 C# 项目选择 serviceInstaller1,或者为 Visual Basic 项目选择 ServiceInstaller1,然后从快捷菜单中选择“属性”。
在 “属性” 窗口中,将 ServiceName 属性设置为 MyNewService。
向属性添加文本 Description ,例如 示例服务。
此文本显示在“服务”窗口的“说明”列中,并向用户描述服务。
向 DisplayName 属性添加文本。 例如 MyNewService 显示名称。
此文本显示在“服务”窗口的“显示名称”列中。 此名称可以不同于 ServiceName 属性,这是系统使用的名称(例如,用于
net start启动服务的命令的名称)。完成后, “属性” 窗口应如下图所示:
在 ProjectInstaller的设计视图中,为 C# 项目选择 serviceProcessInstaller1,或者为 Visual Basic 项目选择 ServiceProcessInstaller1,然后从快捷菜单中选择“属性”。 将 Account 属性设置为 LocalSystem,从下拉列表中选择。
此设置安装该服务并使用本地系统帐户运行该服务。
重要
该 LocalSystem 帐户具有广泛的权限,包括写入事件日志的功能。 请谨慎使用此帐户,因为它可能会增加来自恶意软件的攻击风险。 对于其他任务,请考虑使用 LocalService 帐户,该帐户充当本地计算机上的非特权用户,并向任何远程服务器提供匿名凭据。 但是,如果尝试使用该 LocalService 帐户,此示例将失败,因为它需要写入事件日志的权限。
有关安装程序的详细信息,请参阅 如何:将安装程序添加到服务应用程序。
(可选)设置启动参数
注释
在决定添加启动参数之前,请考虑是否是将信息传递给服务的最佳方式。 尽管它们易于使用和分析,并且用户可以轻松替代它们,但用户可能很难在没有文档的情况下发现和使用它们。 通常,如果服务只需要几个启动参数,则应改用注册表或配置文件。
Windows 服务可以接受命令行参数,也称为 启动参数。 添加代码来处理启动参数时,用户可以在服务属性窗口中使用自己的自定义启动参数启动服务。 但是,下次服务启动时,这些启动参数不会持久化。
若要永久设置启动参数,请在注册表中设置它们。 每个 Windows 服务在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services 子项下都有一个注册表项。 在每个服务的子项下,使用 Parameters 子项来存储服务可以访问的信息。 可以将应用程序配置文件用于 Windows 服务,就像对其他类型的程序一样。 有关示例代码,请参阅 ConfigurationManager.AppSettings。
添加启动参数
在 MyNewService.cs或 MyNewService.vb中,更改
MyNewService构造函数以接受和处理输入参数:public MyNewService(string[] args) { InitializeComponent(); string eventSourceName = "MySource"; string logName = "MyNewLog"; if (args.Length > 0) { eventSourceName = args[0]; } if (args.Length > 1) { logName = args[1]; } eventLog1 = new EventLog(); if (!EventLog.SourceExists(eventSourceName)) { EventLog.CreateEventSource(eventSourceName, logName); } eventLog1.Source = eventSourceName; eventLog1.Log = logName; }Public Sub New(ByVal cmdArgs() As String) InitializeComponent() Dim eventSourceName As String = "MySource" Dim logName As String = "MyNewLog" If (cmdArgs.Count() > 0) Then eventSourceName = cmdArgs(0) End If If (cmdArgs.Count() > 1) Then logName = cmdArgs(1) End If eventLog1 = New EventLog() If (Not EventLog.SourceExists(eventSourceName)) Then EventLog.CreateEventSource(eventSourceName, logName) End If eventLog1.Source = eventSourceName eventLog1.Log = logName End Sub此代码根据用户提供的启动参数设置事件源和日志名称。 如果未提供任何参数,则使用默认值。
选择 Program.cs或 MyNewService.Designer.vb,然后从快捷菜单中选择 “查看代码 ”。 在方法中
Main,更改代码以添加输入参数并将其传递给服务构造函数:static void Main(string[] args) { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new MyNewService(args) }; ServiceBase.Run(ServicesToRun); }Shared Sub Main(ByVal cmdArgs() As String) Dim ServicesToRun() As System.ServiceProcess.ServiceBase = New System.ServiceProcess.ServiceBase() {New MyNewService(cmdArgs)} System.ServiceProcess.ServiceBase.Run(ServicesToRun) End Sub若要指定命令行参数,请将以下代码添加到
ProjectInstallerProjectInstaller.cs 中的类,或 ProjectInstaller.vb:protected override void OnBeforeInstall(IDictionary savedState) { string parameter = "MySource1\" \"MyLogFile1"; Context.Parameters["assemblypath"] = "\"" + Context.Parameters["assemblypath"] + "\" \"" + parameter + "\""; base.OnBeforeInstall(savedState); }Protected Overrides Sub OnBeforeInstall(ByVal savedState As IDictionary) Dim parameter As String = "MySource1"" ""MyLogFile1" Context.Parameters("assemblypath") = """" + Context.Parameters("assemblypath") + """ """ + parameter + """" MyBase.OnBeforeInstall(savedState) End Sub通常,此值包含 Windows 服务的可执行文件的完整路径。 若要使服务正确启动,用户必须提供路径和每个单独参数的引号。 用户可以更改 ImagePath 注册表项中的参数,以更改 Windows 服务的启动参数。 但是,更好的方法是以编程方式更改值,以用户友好的方式公开功能,例如使用管理或配置实用工具。
构建服务
在解决方案资源管理器中,从 MyNewService 项目的快捷菜单中选择“属性”。
在 “应用程序 ”选项卡上的 “启动对象 ”列表中,选择 “MyNewService.Program ”(或 Visual Basic 项目的 Sub Main )。
若要生成项目,请在 解决方案资源管理器中,从项目的快捷菜单中选择 “生成 ”(或按 Ctrl+Shift+B)。
安装服务
生成 Windows 服务后,即可安装它。 若要安装 Windows 服务,必须在安装了 Windows 服务的计算机上拥有管理员凭据。
使用管理凭据 打开 Visual Studio 的开发人员命令提示符 。
在 Visual Studio 开发人员命令提示符中,导航到包含项目输出的文件夹(默认情况下,项目 \bin\Debug 子目录)。
输入以下命令:
installutil MyNewService.exe如果服务成功安装,命令将报告成功。
如果系统找不到 installutil.exe,请确保计算机上存在它。 此工具随 .NET Framework 一起安装到 \Microsoft.NET\Framework[64]\<framework 版本>的文件夹%windir%。
如果 installutil.exe 进程失败,请检查安装日志以了解原因。 默认情况下,日志与服务可执行文件位于同一文件夹中。 安装可能会失败,如果:
- 该 RunInstallerAttribute 类不存在于
ProjectInstaller该类中。 - 属性未设置为
true. - 该
ProjectInstaller类未定义为public. - 未以管理员身份打开 VS 开发人员命令提示符。
- 该 RunInstallerAttribute 类不存在于
有关详细信息,请参阅 如何:安装和卸载服务。
启动并运行服务
在 Windows 中,打开 Services 桌面应用:按 Windows+R 打开 “运行 ”框,输入 services.msc,然后按 Enter 或选择“ 确定”。
应会看到 服务中列出的服务,按为其设置的显示名称按字母顺序显示。
若要启动服务,请从服务的快捷菜单中选择 “开始 ”。
若要停止服务,请从服务的快捷菜单中选择 “停止 ”。
(可选)在命令行中,使用命令 net start <服务名称和>net stop <服务名称> 启动和停止服务。
验证服务的事件日志输出
在 Windows 中,打开 事件查看器 桌面应用:在 Windows 搜索栏中输入 事件查看器 ,然后从搜索结果中选择 事件查看器 。
小窍门
在 Visual Studio 中,可以通过从“视图”菜单(或按 Ctrl++)打开服务器资源管理器并展开本地计算机的事件日志节点来访问事件日志。
在 事件查看器中,展开 “应用程序和服务日志”。
如果遵循了添加命令行参数的过程,请找到 MyNewLog (或 MyLogFile1 )的列表并展开它。 你应该能看到你的服务执行的两个动作(启动和停止)的条目。
清理资源
如果不再需要 Windows 服务应用,可以将其删除。
使用管理凭据 打开 Visual Studio 的开发人员命令提示符 。
在 Visual Studio 开发人员命令提示符 窗口中,导航到包含项目的可执行文件的文件夹。
输入以下命令:
installutil.exe /u MyNewService.exe如果服务成功卸载,命令将报告已成功删除服务。 有关详细信息,请参阅 如何:安装和卸载服务。
后续步骤
创建服务后,可以:
创建独立安装程序供其他人用来安装 Windows 服务。 使用 WiX 工具集 为 Windows 服务创建安装程序。 有关其他想法,请参阅 “创建安装程序包”。
浏览组件 ServiceController ,使你能够将命令发送到已安装的服务。
安装应用程序时,使用安装程序创建事件日志,而不是在应用程序运行时创建事件日志。 卸载应用程序时,安装程序会删除事件日志。 有关详细信息,请参阅 EventLogInstaller。