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

参考

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です