タグ別アーカイブ: Windows Service

C#でWindowsサービスのexeにインストーラと自動起動を組み込む

はじめに

C#でのWindowsサービス開発のはじめ方でとりあえずサービスの開発はできるのだが、インストールがめんどくさいのとインストールしたあとに自動で起動してくれないので、これをexe自身でできるように機能を組み込んでみる。

細かいことを考えるよりテンプレートとして使うほうがラクなので、一番下にProgram.csの内容をすべて載せておく。簡単な説明を見たあとでもいいし、面倒なら丸ごとコピペすればそれなりに動くと思う。

主要クラスの説明

インストール・アンインストールはManagedInstallerClassクラス

サービスをインストール・アンインストールにはManagedInstallerClassクラスのInstallHelper(string[])メソッドを使う。このメソッドは内部的にInstallUtil.exeを呼び出しているらしい。

インストール時は引数にexeのパスだけ、アンインストール時は”/u”とexeのパスを渡すだけだ。

var myAssembly = System.Reflection.Assembly.GetEntryAssembly();
string path = myAssembly.Location;
var param = (isInstallMode) ? new[] { path } : new[] { "/u", path };
ManagedInstallerClass.InstallHelper(param);

起動(開始)・停止はServiceControllerクラス

サービスの開始や停止にはServiceControllerクラスのStart()メソッド、Stop()メソッドを使う。ServiceControllerのコンストラクタにサービス名を渡すだけでよい。

起動(開始)

ServiceController sc = new ServiceController(SERVICE_NAME);
if (sc.Status != ServiceControllerStatus.Running)
{
    sc.Start();
}

停止

ServiceController sc = new ServiceController(SERVICE_NAME);
if (sc.Status != ServiceControllerStatus.Stopped)
{
    sc.Stop();
}

全体的なロジック

Mainメソッドではコマンドライン引数が渡されていれば、コンソールモードとして起動する。

コンソールモード(RunAsConsoleModeメソッド内)では引数に応じて、インストール(/i)・アンインストール(/u)・開始(/start)・停止(/stop)を行う。

インストールでは終了後に自動的に開始し、アンインストールでは停止してからアンインストールを行うようにしてある。

ワンポイント

呼び出し方

exeファイル名をAwesomeService.exeだとすると

AwesomeService.exe /i

などとなる。

ソースコード (Program.cs)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace WindowsService1
{
    static class Program
    {
        public const string SERVICE_NAME = "AwesomeService";
        public const string DISPLAY_NAME = "Awesome Service";

        static void Main(string[] args)
        {
            // Run as console mode when an argument is provided.
            if (1 <= args.Length)
            {
                RunAsConsoleMode(args[0]);
                return;
            }

            // Run as Windows Service
            ServiceBase.Run(new Service1()
            {
                CanShutdown = true,
                CanPauseAndContinue = false,
            });
        }

        /// <summary>
        /// Run as console mode when an argument is provided.
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        static void RunAsConsoleMode(string arg)
        {
            string mode = arg.ToLower();
            var myAssembly = System.Reflection.Assembly.GetEntryAssembly();
            string path = myAssembly.Location;

            if (mode == "/i" || mode == "/u")
            {
                bool isInstallMode = (mode == "/i");
                var mes = (isInstallMode) ? "installed" : "uninstalled";
                if (IsServiceExists())
                {
                    Console.WriteLine("{0} has been already {1}.", DISPLAY_NAME, mes);
                }
                else
                {
                    if (!isInstallMode) { StopService(); }
                    var param = (mode == "/i") ? new[] { path } : new[] { "/u", path };
                    ManagedInstallerClass.InstallHelper(param);
                    Console.WriteLine("{0} has been successfully {1}.", DISPLAY_NAME, mes);
                    if (isInstallMode) { StartService(); }
                }
            }
            else if (mode == "/start")
            {
                StartService();
            }
            else if (mode == "/stop")
            {
                StopService();
            }
            else
            {
                Console.WriteLine("Provided arguments are unrecognized.");
            }
        }

        /// <summary>
        /// Whether the service already exists in the computer or not.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        static bool IsServiceExists(string name = SERVICE_NAME)
        {
            ServiceController[] services = ServiceController.GetServices();
            return services.Any(s => s.ServiceName == name);
        }

        /// <summary>
        /// Start the service.
        /// </summary>
        /// <returns></returns>
        static void StartService()
        {
            if (IsServiceExists())
            {
                Console.WriteLine("Starting {0}...", SERVICE_NAME);
                ServiceController sc = new ServiceController(SERVICE_NAME);
                if (sc.Status == ServiceControllerStatus.Running)
                {
                    Console.WriteLine("{0} has been already started.", DISPLAY_NAME);
                }
                else
                {
                    try
                    {
                        sc.Start();
                        Console.WriteLine("{0} has been started.", DISPLAY_NAME);
                    }
                    catch (Exception)
                    {
                        Console.WriteLine("{0} could not be started.", DISPLAY_NAME);
                    }
                }
            }
        }

        /// <summary>
        /// Stop the service.
        /// </summary>
        /// <returns></returns>
        static void StopService()
        {
            if (IsServiceExists())
            {
                Console.WriteLine("Stopping {0}...", SERVICE_NAME);
                ServiceController sc = new ServiceController(SERVICE_NAME);
                if (sc.Status == ServiceControllerStatus.Stopped)
                {
                    Console.WriteLine("{0} has been already stopped.", DISPLAY_NAME);
                }
                else
                {
                    try
                    {
                        sc.Stop();
                        Console.WriteLine("{0} has been stopped.", DISPLAY_NAME);
                    }
                    catch (Exception)
                    {
                        Console.WriteLine("{0} could not be stopped.", DISPLAY_NAME);
                    }
                }
            }
        }

    }

    [RunInstaller(true)]
    public class ProjectInstaller : Installer
    {
        public ProjectInstaller()
        {
            var spi = new ServiceProcessInstaller
            {
                Account = ServiceAccount.LocalSystem
            };
            var si = new ServiceInstaller
            {
                ServiceName = Program.SERVICE_NAME,
                DisplayName = Program.DISPLAY_NAME,
                Description = "Awesome Service.",
                StartType = ServiceStartMode.Automatic,
            };
            this.Installers.Add(spi);
            this.Installers.Add(si);
        }
    }
}

参考

C#でWindowsサービス開発のはじめ方

準備

  • 表示名 (DisplayName):サービス一覧などで「名前」に表示される名前(スペースを含んでもよいし、日本語も可)
  • サービス名 (ServiceName):サービスの識別用の名前(英数字のみ。スペースなども含まない)

プロジェクトの作成

プロジェクトの新規作成で右上の検索ボックスに “サービス” と入力するのが一番手っ取り早い。

通常通り、デフォルトではプロジェクト名がサービスのexe名になるので、先に考えたサービス名を入力しておく。

ここでデザイナからインストーラクラスを追加することもできるが、コードで書いたほうが短くて使いやすいので、コード側で用意することにする。

Program.csの編集

Program.csを開く。

Mainメソッドはそのままでもかまわないが、なぜか冗長な書き方なので、下記のように変更する。

CanShutdowntrueにしておくとPCがシャットダウンするときのイベントを拾うことができる。凝った作りにしない限り一時停止と再開はできなくてもいいと思うので、適宜CanPauseAndContinuefalseにしておく。

static void Main(string[] args)
{
    ServiceBase.Run(new SKYMAgentService()
    {
        CanShutdown = true,
        CanPauseAndContinue = false,
    });
}

Programクラスの下に下記のProjectInstallerクラスを追加する(誤ってProgramクラスの中に書かないこと)。当然別ファイルに分けてもよい。

[RunInstaller(true)]
public class ProjectInstaller : Installer
{
    public ProjectInstaller()
    {
        var spi = new ServiceProcessInstaller
        {
            Account = ServiceAccount.LocalSystem
        };
        var si = new ServiceInstaller
        {
            ServiceName = "サービス名",
            DisplayName = "表示名",
            Description = "サービスの説明",
            StartType = ServiceStartMode.Automatic,
        };
        this.Installers.Add(spi);
        this.Installers.Add(si);
    }
}

Serviceクラスの実装

Service1.csのデザイナのプロパティにServiceNameがあるのでこれもサービス名に統一しておく。

デザイナからコードビューに切り替えるとOnStartOnStopが存在しているので、この中に開始時の処理と停止時の処理を書く。OnShutdownが必要な場合は下記のように追加する。

protected override void OnShutdown()
{
    server.Stop();
}

あとはビルドすればbin\Debugかbin\Releaseにexeが生成される。

インストールとアンインストール

インストールとアンインストールにはInstalUtil.exeを使う。このコマンドはVSにパスの通ったコマンドプロンプトでないといけないので、スタート→プログラム→VS→Visual Studio コマンドプロンプトを管理者権限で起動する。

cd "exeのあるフォルダ"
installutil "exeパス"

でインストールできる。ちなみにアンインストールはinstallutil /u "exeパス"でできる。

参考