2014年1月20日 星期一

Windows Service 開發

Windows Service是一種沒有UI,開機後不需使用者登入就會執行的應用程式,通常會用來做一些定時排程的工作。
用C#開發Windows Service很簡單,以下用一個簡單的範例來介紹

首先開一個新專案,名稱為MyService

在方案總管中可以看到,只有Program.cs和Service1.cs兩個檔案

先來看看Program.cs,這是應用程式的進入點,主要是透過ServiceBase.Run來啟動服務
using System.ServiceProcess;

namespace MyService
{
    static class Program
    {
        /// <summary>
        /// 應用程式的主要進入點。
        /// </summary>
        static void Main()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[] 
            { 
                new Service1() 
            };
            ServiceBase.Run(ServicesToRun);
        }
    }
}

再來看一下Service1.cs,可以看到是繼承自ServiceBase的類別,再自行覆寫要處理的事件
using System.ServiceProcess;

namespace MyService
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
        }

        protected override void OnStop()
        {
        }
    }
}

可覆寫的方法如下
OnContinue 指定暫停服務後要繼續正常運作所要執行的動作
OnCustomCommand 指定在具有指定參數值的命令發生時所要執行的動作
需要注意命令編號為128~256
OnPause 指定在服務暫停時所要執行的動作
OnPowerEvent 這適用於攜帶型電腦,當它們進入暫停模式的時候,不同於系統關閉
OnSessionChange 當從 Terminal Server 工作階段接收到變更事件時執行
OnShutdown 指定緊接在系統關閉之前應該發生的處理
OnStart 指定在服務啟動時所要執行的動作
OnStop 指定在服務停止執行時所要執行的動作

可設定的屬性如下,最常用的還是ServiceName這個屬性

更詳細的內容可以參考MSDN關於ServiceBase的介紹

先來簡單地寫個定時功能
透過Log元件輸出目前時間到Log2Console
相關內容可以參考之前的筆記
Common.Logging
好用的LogViewer

using System;
using System.ServiceProcess;
using System.Timers;
using Common.Logging;

namespace MyService
{
    public partial class Service1 : ServiceBase
    {
        private Timer timer;
        private ILog log;
        private string datetimeFormat = "yyyy/MM/dd HH:mm:ss";
        private double timerInterval = 1000;

        public Service1()
        {
            InitializeComponent();
            this.log = LogManager.GetLogger(typeof(Service1));
            this.timer = new Timer();
            this.timer.Interval = this.timerInterval;
            this.timer.AutoReset = false;
            this.timer.Enabled = false;
            this.timer.Elapsed += Timer_Elapsed;
        }

        protected override void OnStart(string[] args)
        {
            this.timer.Enabled = true;
        }

        protected override void OnStop()
        {
            this.timer.Enabled = false;
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            this.timer.Stop();

            try
            {
                this.log.TraceFormat("now:{0}", DateTime.Now.ToString(this.datetimeFormat));
            }
            catch (Exception ex)
            {
                this.log.Error(ex);
            }

            this.timer.Start();
        }
    }
}



接下來要增加安裝程式,在Service1.cs的設計畫面按右鍵,選擇加入安裝程式

會增加一個繼承自System.Configuration.Install.Installer的ProjectInstaller類別

先設定服務執行時使用的帳號,比較安全的帳號是NetworkService,如果有需要存取資源的話,再去允許權限或是改用LocalSystem這個帳號

接下來設定服務的啟動方式,最常用的當然是自動啟動

DisplayName是名稱,Description描述

設定好後就完成這個Service,編譯後產生執行檔,接下來就要把這個服務安裝的到作業系統中
比較正規的作法是去新增一個安裝專案,但為了方便開發起見,以下用批次檔的方式來安裝

新增幾個批次檔,並改成永遠複製

install.bat用來安裝
@echo off
set InstallUtil=%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
if not exist %InstallUtil% set InstallUtil=%WINDIR%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe
if not exist %InstallUtil% set InstallUtil=%WINDIR%\Microsoft.NET\Framework\v3.5\InstallUtil.exe
if not exist %InstallUtil% (
 echo InstallUtil.exe not found
 exit
)
 
%InstallUtil% -i MyService.exe

restart.bat用來重新啟動服務
net stop Service1
net start Service1

start.bat用來啟動服務
net start Service1

stop.bat用來停止服務
net stop Service1

uninstall.bat用來反安裝
@echo off
set InstallUtil=%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
if not exist %InstallUtil% set InstallUtil=%WINDIR%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe
if not exist %InstallUtil% set InstallUtil=%WINDIR%\Microsoft.NET\Framework\v3.5\InstallUtil.exe
if not exist %InstallUtil% (
 echo InstallUtil.exe not found
 exit
)

%InstallUtil% -u MyService.exe

注意檔案編碼為ANSI格式,UTF8格式在command pro中執行會有亂碼
批次檔是透過InstallUtil.exe這個工具程式來安裝和反安裝,詳細的參數請參考MSDN的說明

點擊install.bat,安裝成功

點繫start.bat,啟動服務

順利的話就可以在Log2Console中定時收到目前時間



要移除服務就點擊uninstall.bat,反安裝成功