Popular Posts
javax.net.ssl.SSLHandshakeException: Connection closed by peer in Android 5.0 Lollipop Recently, there is a error occurs when access website via ssl connection like below although it worked fine several days ago. // Enable SSL... Close window without confirm (I.E only) window.opener=null; window.open('','_self'); window.close(); android.intent.action.SCREEN_ON & android.intent.action.SCREEN_OFF First, I've tried create a receiver to receive screen on/off and register receiver on AndroidManifest.xml like below, but unfortunately ...
Blog Archive
Stats
Window service : dynamic load dll module and execute

Project(DLL) : ServicePlugin

Configuration

Duration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Configuration
{
    public enum Duration
    {
        Day,
        Week,
        Month
    }
}
Logger.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    public class Logger : ConfigurationElement
    {
        /// <summary>
        /// DLL檔案位置
        /// </summary>
        [ConfigurationProperty("binary")]
        public string Binary
        {
            get { return (string)this["binary"]; }
            set { this["binary"] = value; }
        }
        /// <summary>
        /// 實體名稱
        /// </summary>
        [ConfigurationProperty("assembly", IsRequired = true)]
        public string Assembly
        {
            get { return (string)this["assembly"]; }
            set { this["assembly"] = value; }
        }
    }
}
MemoryUsage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    /// <summary>
    /// 已不使用
    /// </summary>
    public sealed class MemoryUsage : ConfigurationElement
    {
        [ConfigurationProperty("interval", IsRequired = false, DefaultValue = 60)]
        public int Interval
        {
            get { return (int)this["interval"]; }
            set { this["interval"] = value; }
        }

        [ConfigurationProperty("monitor", IsRequired = false, DefaultValue = false)]
        public bool Monitor
        {
            get { return (bool)this["monitor"]; }
            set { this["monitor"] = value; }
        }
    }
}
Module.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Runtime.Serialization;
using ScheduleService.Remoting;

namespace ScheduleService.Configuration
{
    public sealed class Module : ConfigurationElement
    {
        /// <summary>
        /// 模組名稱
        /// </summary>
        [ConfigurationProperty("name", IsKey = true)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }
        /// <summary>
        /// 介面名稱
        /// </summary>
        [ConfigurationProperty("type", IsRequired = true)]
        public string Type
        {
            get { return (string)this["type"]; }
            set { this["type"] = value; }
        }
        /// <summary>
        /// DLL檔案位置
        /// </summary>
        [ConfigurationProperty("binary")]
        public string Binary
        {
            get { return (string)this["binary"]; }
            set { this["binary"] = value; }
        }
        /// <summary>
        /// 實體名稱
        /// </summary>
        [ConfigurationProperty("assembly", IsRequired = true)]
        public string Assembly
        {
            get { return (string)this["assembly"]; }
            set { this["assembly"] = value; }
        }
        /// <summary>
        /// 執行間隔(秒)
        /// </summary>
        [ConfigurationProperty("interval", IsRequired = false)]
        public int Interval
        {
            get { return (int)this["interval"]; }
            set { this["interval"] = value; }
        }

        /// <summary>
        /// 執行週期
        /// </summary>
        [ConfigurationProperty("duration", IsRequired = false, DefaultValue = Duration.Day)]
        public Duration Duration
        {
            get { return (Duration)this["duration"]; }
            set { this["duration"] = value; }
        }

        /// <summary>
        /// 執行時間點(格式tt:MM:dd)
        /// </summary>
        [ConfigurationProperty("executePoint", IsRequired = false)]
        public string ExecutePoint
        {
            get { return (string)this["executePoint"]; }
            set { this["executePoint"] = value; }
        }

        /// <summary>
        /// 執行失敗時, 嘗試重新執行一次
        /// </summary>
        [ConfigurationProperty("reTryOnFailed", IsRequired = false, DefaultValue = false)]
        public bool ReTryOnFailed
        {
            get { return (bool)this["reTryOnFailed"]; }
            set { this["reTryOnFailed"] = value; }
        }

        public DateTime StartTime { get; set; }
        public DateTime LastExecuted { get; set; }
        public DateTime NextExecute { get; set; }
        public Exception Exception { get; set; }

        public override string ToString()
        {
            return string.Format("<module name=\"{0}\" type=\"{1}\" binary=\"{2}\" assembly=\"{3}\" interval=\"{4}\" />", this.Name, this.Type, this.Binary, this.Assembly, this.Interval);
        }

        public override int GetHashCode()
        {
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly).GetHashCode();
        }

        public override bool Equals(object compareTo)
        {
            if (compareTo == null || !(compareTo is Module)) return false;
            Module obj = compareTo as Module;
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly)
                == string.Format("{0}{1}{2}{3}", obj.Name, obj.Type, obj.Binary, obj.Assembly);
        }

        public ModuleData ToModuleData()
        {
            ModuleData data = new ModuleData();
            data.Assembly = this.Assembly;
            data.Binary = this.Binary;
            data.Exception = this.Exception;
            data.Interval = this.Interval;
            data.LastExecuted = this.LastExecuted;
            data.Name = this.Name;
            data.NextExecute = this.NextExecute;
            data.StartTime = this.StartTime;
            data.Type = this.Type;
            data.Duration = this.Duration;
            data.ExecutePoint = this.ExecutePoint;
            data.ReTryOnFailed = this.ReTryOnFailed;
            return data;
        }
    }
}
ModuleCollection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;
using System.Configuration;

namespace ScheduleService.Configuration
{
    public sealed class ModuleCollection : ConfigurationElementCollection
    {
        public override ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return ConfigurationElementCollectionType.AddRemoveClearMap;
            }
        }

        public Module this[int index]
        {
            get { return (Module)BaseGet(index); }
            set
            {
                if (BaseGet(index) != null)
                    BaseRemove(index);
                BaseAdd(index, value);
            }
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new Module();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as Module).Name;
        }

        #region Operation
        public void Add(Module module)
        {
            BaseAdd(module);
        }

        public void Clear()
        {
            BaseClear();
        }

        public void Rmove(Module module)
        {
            BaseRemove(module.Name);
        }

        public void Remove(string name)
        {
            BaseRemove(name);
        }

        public void RemoveAt(int index)
        {
            BaseRemoveAt(index);
        }
        #endregion
    }
}
MonitorPort.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;


namespace ScheduleService.Configuration
{
    public sealed class MonitorPort : ConfigurationElement
    {
        [ConfigurationProperty("value", IsRequired = true)]
        public int Value
        {
            get { return (int)this["value"]; }
            set { this["value"] = value; }
        }
    }
}
Plugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    public sealed class Plugin : ConfigurationSection
    {
        ///// <summary>
        ///// 重載間隔
        ///// </summary>
        //[ConfigurationProperty("reloadInterval")]
        //public ReloadInterval ReloadConfigInterval
        //{
        //    get { return (ReloadInterval)this["reloadInterval"]; }
        //    set { this["reloadInterval"] = value; }
        //}
        ///// <summary>
        ///// 監控記憶體使用情況
        ///// </summary>
        //[ConfigurationProperty("memoryUsage")]
        //public MemoryUsage MemoryUsage
        //{
        //    get { return (MemoryUsage)this["memoryUsage"]; }
        //    set { this["memoryUsage"] = value; }
        //}
        /// <summary>
        /// 監控通訊埠
        /// </summary>
        [ConfigurationProperty("monitorPort")]
        public MonitorPort MonitorPort
        {
            get { return (MonitorPort)this["monitorPort"]; }
            set { this["monitorPort"] = value; }
        }
        /// <summary>
        /// 外掛log
        /// </summary>
        [ConfigurationProperty("exceptionHandler")]
        public Logger ExceptionHandler
        {
            get { return (Logger)this["exceptionHandler"]; }
            set { this["exceptionHandler"] = value; }
        }
        /// <summary>
        /// 模組
        /// </summary>
        [ConfigurationProperty("plugins")]
        [ConfigurationCollection(typeof(ModuleCollection), AddItemName = "module", ClearItemsName = "clearModules", RemoveItemName = "removeModules")]
        public ModuleCollection Plugins
        {
            get { return (ModuleCollection)this["plugins"]; }
            set { this["plugins"] = value; }
        }
    }
}
ReloadInterval.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    /// <summary>
    /// 已不使用
    /// </summary>
    public sealed class ReloadInterval : ConfigurationElement
    {
        [ConfigurationProperty("value", IsRequired = true)]
        public int Value
        {
            get { return (int)this["value"]; }
            set { this["value"] = value; }
        }
    }
}

Plugin

IAlarmPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IAlarmPlugin
    {
        void ExecuteAt(object sender, System.Timers.ElapsedEventArgs e);
    }
}
IComboPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IComboPlugin
    {
        bool OnExecuting();
        void OnExecuted();
    }
}
IComplexPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IComplexPlugin : ISimplePlugin, IComboPlugin, IGeneralPlugin
    {

    }
}
IGeneralPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IGeneralPlugin
    {
        bool OnStart();
        void OnStop();
    }
}
ISimplePlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface ISimplePlugin
    {
        void Execute(object sender, System.Timers.ElapsedEventArgs e);
    }
}
PluginLoader.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Configuration;
using ScheduleService.Configuration;
using ScheduleService.Utilily;

namespace ScheduleService.Plugin
{
    public class PluginLoader
    {
        private List<Module> LoadedPlugins;
        /// <summary>
        /// 載入目前執行個體的設定檔
        /// </summary>
        public void LoadConfig()
        {
            LoadConfig(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath);
        }
        /// <summary>
        /// 載入指定的設定檔
        /// </summary>
        /// <param name="configPath"></param>
        public void LoadConfig(string configPath)
        {
            try
            {
                LoadedPlugins = new List<Module>();
                #region 重新載入設定檔
                ConfigurationManager.RefreshSection("scheduleService.plugin");
                ModuleCollection modules = ((Configuration.Plugin)ConfigurationManager.GetSection("scheduleService.plugin")).Plugins;
                foreach (Module m in modules)
                {
                    LoadedPlugins.Add(m);
                }
                #endregion
                #region 以讀檔方式重新載入設定檔
                //XElement root = XElement.Load(configPath);
                //IEnumerator<XElement> itor = root.XPathSelectElements("//scheduleService.plugin/plugins/module").GetEnumerator();
                //while (itor.MoveNext())
                //{
                //    try
                //    {
                //        Module m = new Module();
                //        m.Assembly = itor.Current.Attribute("assembly").Value;
                //        m.Binary = itor.Current.Attribute("binary").Value;
                //        m.Name = itor.Current.Attribute("name").Value;
                //        m.Type = itor.Current.Attribute("type").Value;
                //        m.Interval = int.Parse(itor.Current.Attribute("interval").Value);
                //        LoadedPlugins.Add(m);
                //    }
                //    catch (Exception ex)
                //    {
                //        ServiceLogger.Log(string.Format("載入模組{0}時發生錯誤", itor.Current.ToString()), ex);
                //    }
                //}
                #endregion
            }
            catch (Exception ex)
            {
                ServiceLogger.Log(string.Format("載入設定檔{0}時發生錯誤", configPath), ex);
            }
        }
        /// <summary>
        /// 取得指定模組資料
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Module GetModule(string name)
        {
            if (LoadedPlugins == null)
            {
                LoadConfig();
            }

            foreach (Module m in LoadedPlugins)
            {
                if (m.Name == name)
                    return m;
            }
            return null;
        }
        /// <summary>
        /// 取得所有模組
        /// </summary>
        /// <returns></returns>
        public Module[] GetModules()
        {
            if (LoadedPlugins == null)
            {
                LoadConfig();
            }

            return LoadedPlugins.ToArray();
        }
        /// <summary>
        /// 取得所有模組設定字串
        /// </summary>
        /// <returns></returns>
        public string[] GetModuleStrings()
        {
            if (LoadedPlugins == null)
            {
                LoadConfig();
            }

            List<string> list = new List<string>();
            foreach (Module m in LoadedPlugins)
            {
                list.Add(m.ToString());
            }
            return list.ToArray();
        }
    }
}
Timer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using ScheduleService.Configuration;
using ScheduleService.Utilily;
using System.Text.RegularExpressions;

namespace ScheduleService.Plugin
{
    public class Timer : System.Timers.Timer
    {
        private bool flag;
        /// <summary>
        /// 模組的實體物件
        /// </summary>
        public object Instance { get; private set; }
        /// <summary>
        /// 執行模組的資料
        /// </summary>
        public ScheduleService.Configuration.Module Source { get; private set; }

        public Timer(ScheduleService.Configuration.Module module)
            : base()
        {
            try
            {
                Source = module;
                base.Enabled = true;


                Assembly assembly = Assembly.LoadFrom(PathHelper.PluginPath + Source.Binary);
                switch (Source.Type)
                {
                    case "ScheduleService.Plugin.ISimplePlugin":
                    case "ScheduleService.Plugin.IComplexPlugin":
                        ISimplePlugin simplePlugin = (ISimplePlugin)assembly.CreateInstance(Source.Assembly);
                        base.Interval = module.Interval * 1000;
                        base.Elapsed += new System.Timers.ElapsedEventHandler(Simple_Elapsed);
                        this.Instance = simplePlugin;
                        this.Source.NextExecute = DateTime.Now.AddSeconds(this.Source.Interval);
                        break;
                    case "ScheduleService.Plugin.IAlarmPlugin":  // 定時類型
                        IAlarmPlugin alarmPlugin = (IAlarmPlugin)assembly.CreateInstance(Source.Assembly);
                        base.Interval = 30000;  // 每30秒執行檢查
                        base.Elapsed += new System.Timers.ElapsedEventHandler(Alarm_Elapsed);
                        this.Instance = alarmPlugin;
                        this.Source.NextExecute = this.NextExecute(DateTime.Now);
                        break;
                    case "ScheduleService.Plugin.IGeneralPlugin":  // 未測試
                        IGeneralPlugin generalPlugin = (IGeneralPlugin)assembly.CreateInstance(Source.Assembly);
                        base.Enabled = false;
                        this.Instance = generalPlugin;
                        break;
                }
            }
            catch (Exception ex)
            {
                ServiceLogger.Log(string.Format("建立'{0}'實體時發生錯誤", Source.Assembly), ex);
                throw ex;
            }
        }

        void Alarm_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (this.flag)
            {
                ServiceLogger.Log("前一個處理排程正在進行中,略過本次排程");
                return;
            }

            if (string.Format("{0:yyyyMMddHHmm}", this.NextExecute(DateTime.Now.AddMinutes(-1))) == string.Format("{0:yyyyMMddHHmm}", DateTime.Now))
            {
                this.flag = true;
                this.Enabled = false;
                this.Source.LastExecuted = e.SignalTime; // 執行時間
                this.Source.NextExecute = this.NextExecute(DateTime.Now.AddMinutes(5));  // 預計下次執行時間
                try
                {
                    this.ExecuteAlarm(sender, e);

                    this.Source.Exception = null;
                }
                catch (Exception ex)
                {
                    this.Source.Exception = ex;
                    ServiceLogger.Log(string.Format("排程工作[{0}]執行時發生錯誤", Source.Name), ex);
                    if (this.Source.ReTryOnFailed)
                    {
                        // 延遲30秒後重試
                        System.Threading.Thread.Sleep(30000);
                        ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]", Source.Name), ex);
                        try
                        {
                            this.ExecuteAlarm(sender, e);
                        }
                        catch (Exception exx)
                        {
                            ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]執行時發生錯誤", Source.Name), exx);
                        }
                    }
                }

                // 強迫執行時間大於60秒, 避免重覆執行
                System.Threading.Thread.Sleep(60000);

                this.Enabled = true;
                this.flag = false;

                // 回收記憶體
                GC.Collect();
            }
        }

        private void ExecuteAlarm(object sender, System.Timers.ElapsedEventArgs e)
        {
            IComboPlugin comboPlugin = this.Instance as IComboPlugin;
            IAlarmPlugin alarmPlugin = this.Instance as IAlarmPlugin;

            // OnExecuting
            bool isCanceled = false;
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                isCanceled = !comboPlugin.OnExecuting();
            }

            // Execute
            if (isCanceled)
            {
                ServiceLogger.Log(string.Format("執行模組[{0}]時被前置作業取消", Source.Name));
            }
            else
            {
                ServiceLogger.Log(string.Format("開始執行模組[{0}]", Source.Name));
                alarmPlugin.ExecuteAt(sender, e);
                ServiceLogger.Log(string.Format("排程工作[{0}]執行完成", Source.Name));
            }

            // OnExecuted
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                comboPlugin.OnExecuted();
            }
        }


        void Simple_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (this.flag)
            {
                ServiceLogger.Log("前一個處理排程正在進行中,略過本次排程");
                return;
            }

            this.flag = true;
            this.Enabled = false;
            this.Source.LastExecuted = e.SignalTime; // 執行時間
            this.Source.NextExecute = e.SignalTime.AddSeconds(this.Source.Interval);  // 預計下次執行時間
            try
            {
                this.ExecuteSimple(sender, e);

                this.Source.Exception = null;
            }
            catch (Exception ex)
            {
                this.Source.Exception = ex;
                ServiceLogger.Log(string.Format("排程工作[{0}]執行時發生錯誤", Source.Name), ex);

                if (this.Source.ReTryOnFailed)
                {
                    // 延遲30秒後重試
                    System.Threading.Thread.Sleep(30000);
                    ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]", Source.Name), ex);
                    try
                    {
                        this.ExecuteSimple(sender, e);
                    }
                    catch (Exception exx)
                    {
                        ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]執行時發生錯誤", Source.Name), exx);
                    }
                }
            }

            this.Enabled = true;
            this.flag = false;

            // 回收記憶體
            GC.Collect();
        }

        private void ExecuteSimple(object sender, System.Timers.ElapsedEventArgs e)
        {
            IComboPlugin comboPlugin = this.Instance as IComboPlugin;
            ISimplePlugin simplePlugin = this.Instance as ISimplePlugin;

            // OnExecuting
            bool isCanceled = false;
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                isCanceled = !comboPlugin.OnExecuting();
            }

            // Execute
            if (isCanceled)
            {
                ServiceLogger.Log(string.Format("執行模組[{0}]時被前置作業取消", Source.Name));
            }
            else
            {
                ServiceLogger.Log(string.Format("開始執行模組[{0}]", Source.Name));
                simplePlugin.Execute(sender, e);
                ServiceLogger.Log(string.Format("排程工作[{0}]執行完成", Source.Name));
            }

            // OnExecuted
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                comboPlugin.OnExecuted();
            }
        }

        /// <summary>
        /// 計算下次執行時間
        /// </summary>
        /// <param name="baseTime"></param>
        /// <returns></returns>
        private DateTime NextExecute(DateTime baseTime)
        {
            int duration = -1;
            int hour = -1;
            int minute = -1;
            string[] time = this.Source.ExecutePoint.Split(new char[] { ':' });
            if (Regex.IsMatch(this.Source.ExecutePoint, "^(\\d+:)?\\d+:\\d+$"))
            {
                switch (time.Length)
                {
                    case 3:
                        // 計算週期的值
                        duration = int.Parse(time[0]);
                        switch (this.Source.Duration)
                        {
                            case Duration.Week:
                                if (duration < 0 || duration > 6) throw new FormatException("執行時間點:星期數值錯誤");
                                break;
                            case Duration.Month:
                                if (duration < 0 || duration > 31) throw new FormatException("執行時間點:日期數值錯誤");
                                break;
                        }
                        // 小時值
                        hour = int.Parse(time[1]);
                        if (hour < 0 || hour > 23) throw new FormatException("執行時間點:小時數值錯誤");
                        // 分鐘值
                        minute = int.Parse(time[2]);
                        if (hour < 0 || hour > 59) throw new FormatException("執行時間點:分鐘數值錯誤");
                        break;
                    case 2:
                        // 小時值
                        hour = int.Parse(time[0]);
                        if (hour < 0 || hour > 23) throw new FormatException("執行時間點:小時數值錯誤");
                        // 分鐘值
                        minute = int.Parse(time[1]);
                        if (hour < 0 || hour > 59) throw new FormatException("執行時間點:分鐘數值錯誤");
                        break;
                    default:
                        throw new FormatException("執行時間點:時間格式錯誤");
                }


                DateTime nextExecute = baseTime;
                switch (this.Source.Duration)
                {
                    case Duration.Day:
                        nextExecute = new DateTime(baseTime.Year, baseTime.Month, baseTime.Day, hour, minute, 0);
                        if (nextExecute < baseTime) nextExecute = nextExecute.AddDays(1);
                        break;
                    case Duration.Week:
                        int todayOfWeek = (int)DateTime.Now.DayOfWeek; // 今天的星期
                        int leftDays = duration - todayOfWeek < 0 ? duration - todayOfWeek + 7 : duration - todayOfWeek;  // 距下次執行日期的天數
                        nextExecute = new DateTime(baseTime.Year, baseTime.Month, baseTime.Day, hour, minute, 0).AddDays(leftDays);
                        if (nextExecute < baseTime) nextExecute = nextExecute.AddDays(7);
                        break;
                    case Duration.Month:
                        duration = Math.Min(DateTime.DaysInMonth(baseTime.Year, baseTime.Month), duration);  // 數值大於當月天數時, 在最後一天執行
                        nextExecute = new DateTime(baseTime.Year, baseTime.Month, duration, hour, minute, 0);
                        if (nextExecute < baseTime) nextExecute = nextExecute.AddMonths(1);
                        break;
                }
                return nextExecute;
            }
            else
            {
                throw new FormatException("執行時間點:時間格式錯誤");
            }
        }
    }
}

Remoting

MemoryUsage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ScheduleService.Remoting
{
    [Serializable]
    public class MemoryUsage
    {
        public int NonpagedSystemMemorySize { get; set; }
        public long NonpagedSystemMemorySize64 { get; set; }
        public int PagedMemorySize { get; set; }
        public long PagedMemorySize64 { get; set; }
        public int PagedSystemMemorySize { get; set; }
        public long PagedSystemMemorySize64 { get; set; }
        public int PeakPagedMemorySize { get; set; }
        public long PeakPagedMemorySize64 { get; set; }
        public int PeakVirtualMemorySize { get; set; }
        public long PeakVirtualMemorySize64 { get; set; }
        public int PeakWorkingSet { get; set; }
        public long PeakWorkingSet64 { get; set; }
        public int PrivateMemorySize { get; set; }
        public long PrivateMemorySize64 { get; set; }
        public int VirtualMemorySize { get; set; }
        public long VirtualMemorySize64 { get; set; }
        public int WorkingSet { get; set; }
        public long WorkingSet64 { get; set; }

        protected internal void Refresh()
        {
            Process p = Process.GetCurrentProcess();
            this.NonpagedSystemMemorySize = p.NonpagedSystemMemorySize;
            this.NonpagedSystemMemorySize64 = p.NonpagedSystemMemorySize64;
            this.PagedMemorySize = p.PagedMemorySize;
            this.PagedMemorySize64 = p.PagedMemorySize64;
            this.PagedSystemMemorySize = p.PagedSystemMemorySize;
            this.PagedSystemMemorySize64 = p.PagedSystemMemorySize64;
            this.PeakPagedMemorySize = p.PeakPagedMemorySize;
            this.PeakPagedMemorySize64 = p.PeakPagedMemorySize64;
            this.PeakVirtualMemorySize = p.PeakVirtualMemorySize;
            this.PeakVirtualMemorySize64 = p.PeakVirtualMemorySize64;
            this.PeakWorkingSet = p.PeakWorkingSet;
            this.PeakWorkingSet64 = p.PeakWorkingSet64;
            this.PrivateMemorySize = p.PrivateMemorySize;
            this.PrivateMemorySize64 = p.PrivateMemorySize64;
            this.VirtualMemorySize = p.VirtualMemorySize;
            this.VirtualMemorySize64 = p.VirtualMemorySize64;
            this.WorkingSet = p.WorkingSet;
            this.WorkingSet64 = p.WorkingSet64;
        }
    }
}
ModuleData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;

namespace ScheduleService.Remoting
{
    [Serializable]
    public sealed class ModuleData
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string Binary { get; set; }
        public string Assembly { get; set; }
        public int Interval { get; set; }
        public Duration Duration { get; set; }
        public string ExecutePoint { get; set; }
        public bool ReTryOnFailed { get; set; }

        public DateTime StartTime { get; set; }
        public DateTime LastExecuted { get; set; }
        public DateTime NextExecute { get; set; }
        public Exception Exception { get; set; }


        public override int GetHashCode()
        {
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly).GetHashCode();
        }

        public override bool Equals(object compareTo)
        {
            if (compareTo == null || !(compareTo is ModuleData)) return false;
            ModuleData obj = compareTo as ModuleData;
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly)
                == string.Format("{0}{1}{2}{3}", obj.Name, obj.Type, obj.Binary, obj.Assembly);
        }

    }
}
Monitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;

namespace ScheduleService.Remoting
{
    public interface Monitor
    {
        MemoryUsage GetUsage();

        ModuleData[] GetModules();
    }
}
ServiceClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Net;

namespace ScheduleService.Remoting
{
    public class ServiceClient : Monitor
    {
        private TcpChannel channel;
        private Monitor remotingObj;

        public void Connect(string host, int port)
        {
            if (channel != null)
            {
                this.Close();
            }

            IPAddress[] ip = Dns.GetHostAddresses(host);
            if (ip.Count() == 0) throw new Exception("無法解析主機名稱或ip");

            this.channel = new TcpChannel();
            ChannelServices.RegisterChannel(this.channel, false);

            remotingObj = Activator.GetObject(
                typeof(Monitor),
                string.Format("tcp://{0}:{1}/ScheduleService.Remoting", ip[0], port)
            ) as Monitor;
        }

        public void Close()
        {
            if (this.channel != null)
            {
                ChannelServices.UnregisterChannel(this.channel);
                this.channel.StopListening(null);
                this.channel = null;
            }

            GC.Collect();
        }

        #region Monitor 成員

        public MemoryUsage GetUsage()
        {
            try
            {
                if (remotingObj == null)
                    return null;
                else
                    return remotingObj.GetUsage();
            }
            catch //(Exception ex)
            {
                return null;
            }
        }

        public ModuleData[] GetModules()
        {
            try
            {

                if (remotingObj == null)
                    return null;
                else
                    return remotingObj.GetModules();
            }
            catch //(Exception ex)
            {
                return null;
            }
        }

        #endregion
    }
}
ServiceMonitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;

namespace ScheduleService.Remoting
{
    public class ServiceMonitor : MarshalByRefObject, Monitor
    {
        private TcpChannel channel;

        public void StartListen(int port)
        {
            // 結束既有的監聽
            if (this.channel != null)
            {
                this.StopListen();
            }

            this.channel = new TcpChannel(port);
            ChannelServices.RegisterChannel(this.channel, false);
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServiceMonitor),
                "ScheduleService.Remoting",
                WellKnownObjectMode.SingleCall
            );
        }

        public void StopListen()
        {
            if (this.channel != null)
            {
                ChannelServices.UnregisterChannel(this.channel);
                this.channel.StopListening(null);
                this.channel = null;
            }

            GC.Collect();
        }

        #region Monitor 成員

        public MemoryUsage GetUsage()
        {
            MemoryUsage usage = new MemoryUsage();
            usage.Refresh();
            return usage;
        }

        public ModuleData[] GetModules()
        {
            if (ScheduleServiceBase.ModuleList == null)
            {
                return new ModuleData[0];
            }
            else
            {
                List<ModuleData> list = new List<ModuleData>();
                foreach (ScheduleService.Configuration.Module m in ScheduleServiceBase.ModuleList)
                {
                    list.Add(m.ToModuleData());
                }
                return list.ToArray();
            }
        }

        #endregion
    }
}

Utilily

Convertor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Utilily
{
    public class Convertor
    {
        public static string MatchSize(double size, SizeName sizeName)
        {
            if (size < 1024 || sizeName == SizeName.TB)
            {
                return string.Format("{0:####.##}{1}", size, sizeName);
            }
            else
            {
                return Convertor.MatchSize(size / 1024, (SizeName)(((int)sizeName) + 1));
            }
        }
    }
}
ExceptionHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Utilily
{
    public interface ExceptionHandler
    {
        void Log(string description);
        void Log(string description, Exception ex);
        void Log(string description, Exception ex, object data);
    }
}
FileoutputHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using ScheduleService.Configuration;

namespace ScheduleService.Utilily
{
    public class FileoutputHandler : ScheduleService.Utilily.ExceptionHandler
    {
        #region ExceptionHandler 成員

        public void Log(string description)
        {
            File.AppendAllText(string.Format("{0}ScheduleService{1:yyyyMMdd}.log", PathHelper.LogPath, DateTime.Now),
                string.Format(@"Time : {0}
Description : {1}

", DateTime.Now, description));
        }

        public void Log(string description, Exception ex)
        {
            File.AppendAllText(string.Format("{0}ScheduleService{1:yyyyMMdd}.log", PathHelper.LogPath, DateTime.Now),
                string.Format(@"Time : {0}
Description : {1}
Message : {2}
Source : {3}
Exception type : {4}
Stack : {5}

", DateTime.Now, description, ex.Message, ex.Source, ex.GetType().Name, ex.StackTrace));
        }

        public void Log(string description, Exception ex, object data)
        {
            this.Log(description, ex);
        }

        #endregion
    }
}
PathHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ScheduleService.Utilily
{
    public class PathHelper
    {
        /// <summary>
        /// 程式目錄
        /// </summary>
        public static string ApplicationPath
        {
            get { return AppDomain.CurrentDomain.BaseDirectory; }
        }
        /// <summary>
        /// 模組目錄
        /// </summary>
        public static string PluginPath
        {
            get { return string.Format("{0}Plugin\\", PathHelper.ApplicationPath); }
        }
        /// <summary>
        /// Log目錄
        /// </summary>
        public static string LogPath
        {
            get
            {
                string logPath = string.Format("{0}Log\\", PathHelper.ApplicationPath);
                if (!Directory.Exists(logPath))
                    Directory.CreateDirectory(logPath);
                return logPath;
            }
        }

        public static string TempPath
        {
            get
            {
                string tempPath = string.Format("{0}Temp\\", PathHelper.ApplicationPath);
                if (!Directory.Exists(tempPath))
                    Directory.CreateDirectory(tempPath);
                return tempPath;
            }
        }
    }
}
ServiceLogger.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Utilily;

namespace ScheduleService.Utilily
{
    public class ServiceLogger
    {
        private static ExceptionHandler exHandler;

        public static void SetHandler(ExceptionHandler handler)
        {
            exHandler = handler;
        }

        public static void Log(string description)
        {
            if (exHandler != null)
                exHandler.Log(description);
        }
        public static void Log(string description, Exception ex)
        {
            if (exHandler != null)
                exHandler.Log(description, ex);
        }
        public static void Log(string description, Exception ex, object data)
        {
            if (exHandler != null)
                exHandler.Log(description, ex, data);
        }
    }
}
SizeName.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Utilily
{
    public enum SizeName : int
    {
        Bytes = 1,
        KB = 2,
        MB = 3,
        GB = 4,
        TB = 5
    }
}

ScheduleServiceBase.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Plugin;
using System.Reflection;
using ScheduleService.Configuration;
using System.Diagnostics;
using System.Configuration;
using ScheduleService.Utilily;
using System.IO;
using ScheduleService.Remoting;

namespace ScheduleService
{
    public class ScheduleServiceBase : System.ServiceProcess.ServiceBase
    {
        /// <summary>
        /// 排程的timer
        /// </summary>
        private List<Timer> tasks;
        /// <summary>
        /// 目前載入的模組
        /// </summary>
        private static List<ScheduleService.Configuration.Module> moduleList;
        protected internal static List<ScheduleService.Configuration.Module> ModuleList { get { return ScheduleServiceBase.moduleList; } }
        /// <summary>
        /// 設定檔監控
        /// </summary>
        private FileSystemWatcher configWatcher;
        /// <summary>
        /// 遠端監控
        /// </summary>
        private ServiceMonitor monitor;

        public ScheduleServiceBase()
        {
            this.tasks = new List<Timer>();
            ScheduleServiceBase.moduleList = new List<ScheduleService.Configuration.Module>();
            this.configWatcher = new FileSystemWatcher();
            this.monitor = new ServiceMonitor();

            // 指定Log記錄方式
            ServiceLogger.SetHandler(new FileoutputHandler());
            ServiceLogger.Log("狀態: 啟動服務");

            ScheduleService.Configuration.Plugin plugin = null;
            try
            {
                plugin = (ScheduleService.Configuration.Plugin)ConfigurationManager.GetSection("scheduleService.plugin");
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("初始設定檔時發生錯誤", ex);
            }

            try
            {
                if (plugin != null && plugin.ExceptionHandler != null && !string.IsNullOrEmpty(plugin.ExceptionHandler.Binary))
                {
                    Assembly assembly = Assembly.LoadFrom(PathHelper.PluginPath + plugin.ExceptionHandler.Binary);
                    ExceptionHandler handler = (ExceptionHandler)assembly.CreateInstance(plugin.ExceptionHandler.Assembly);
                    if (handler == null)
                        throw new Exception("建立例外處理器實體失敗");
                    else
                        ServiceLogger.SetHandler(handler);
                }
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("設定例外處理器時發生錯誤", ex);
            }

            try
            {
                // 起始remoting
                this.monitor.StartListen(plugin.MonitorPort.Value);
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("設定遠端監控時發生錯誤", ex);
            }
        }


        /// <summary>
        /// 測試用
        /// </summary>
        /// <param name="args"></param>
        public void DoStart(string[] args)
        {
            this.OnStart(args);
        }
        /// <summary>
        /// 啟始設定檔載入程式
        /// </summary>
        /// <param name="args"></param>
        protected override void OnStart(string[] args)
        {
            try
            {
                #region 監控設定是否改變
                this.configWatcher.Path = PathHelper.ApplicationPath;
                this.configWatcher.Filter = "*.config";
                this.configWatcher.IncludeSubdirectories = false;
                this.configWatcher.EnableRaisingEvents = true;
                this.configWatcher.Changed += new FileSystemEventHandler(configWatcher_Changed);
                #endregion

                // 載入模組
                this.ChangeModule();
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("起動程式時發生錯誤", ex);
            }
        }

        /// <summary>
        /// 異動時新增/移除模組
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void configWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            // 暫止監控
            this.configWatcher.EnableRaisingEvents = false;

            // 設定檔變更時
            if (e.Name == string.Format("{0}.config", AppDomain.CurrentDomain.FriendlyName))
            {
                this.ChangeModule();
            }

            // 恢復監控
            this.configWatcher.EnableRaisingEvents = true;
        }
        /// <summary>
        /// 新增/移除模組
        /// </summary>
        private void ChangeModule()
        {
            try
            {
                PluginLoader loader = new PluginLoader();
                ScheduleService.Configuration.Module[] plugins = loader.GetModules();

                #region 加入新增的模組
                foreach (ScheduleService.Configuration.Module m in plugins)
                {
                    if (!ScheduleServiceBase.moduleList.Contains(m))
                    {
                        try
                        {
                            Timer timer = new Timer(m);
                            // 初始化(OnInit)
                            if (timer.Instance is IGeneralPlugin)
                            {
                                IGeneralPlugin complexPlugin = (IGeneralPlugin)timer.Instance;
                                if (!complexPlugin.OnStart())
                                {
                                    ServiceLogger.Log(string.Format("模組[{0}]初始化時由前置作業終止,未載入模組", m.Name));
                                    continue;
                                }
                            }
                            timer.Start();
                            this.tasks.Add(timer);
                            ScheduleServiceBase.moduleList.Add(m);

                            // 記錄時間
                            m.StartTime = DateTime.Now;
                            ServiceLogger.Log(string.Format("加入新模組[{0}]", m.Name));
                        }
                        catch (Exception ex)
                        {
                            ServiceLogger.Log(string.Format("加入模組[{0}]時發生錯誤", m.Name), ex);
                        }
                    }
                }
                #endregion

                #region 移除模組
                ScheduleService.Configuration.Module[] currentModule = loader.GetModules();
                for (int i = this.tasks.Count - 1; i >= 0; i--)
                {
                    Timer timer = this.tasks[i];
                    try
                    {
                        if (!currentModule.Contains(timer.Source))
                        {
                            timer.Enabled = false;
                            timer.Stop();
                            this.tasks.Remove(timer);
                            ScheduleServiceBase.ModuleList.Remove(timer.Source);
                            // 回收(Dispose)
                            if (timer.Instance is IGeneralPlugin)
                            {
                                IGeneralPlugin generalPlugin = (IGeneralPlugin)timer.Instance;
                                generalPlugin.OnStop();
                            }
                            ServiceLogger.Log(string.Format("已移除模組[{0}]", timer.Source.Name));
                        }
                    }
                    catch (Exception ex)
                    {
                        ServiceLogger.Log(string.Format("移除模組[{0}]時發生錯誤", timer.Source.Name), ex);
                    }
                }
                #endregion

                // 回收記憶體
                GC.Collect();
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("模組異動時發生錯誤", ex);
            }
        }
        /// <summary>
        /// 測試用
        /// </summary>
        public void DoStop()
        {
            this.OnStop();
        }
        protected override void OnStop()
        {
            // 停止監控設定檔
            this.configWatcher.EnableRaisingEvents = false;
            // 停止remoting channel
            this.monitor.StopListen();

            foreach (ScheduleService.Plugin.Timer timer in this.tasks)
            {
                try
                {
                    timer.Enabled = false;
                    timer.Stop();
                    if (timer.Instance is IGeneralPlugin)
                    {
                        IGeneralPlugin generalPlugin = (IGeneralPlugin)timer.Instance;
                        generalPlugin.OnStop();
                    }
                }
                catch (Exception ex)
                {
                    ServiceLogger.Log("停止服務時發生錯誤", ex);
                }
            }
            ServiceLogger.Log("狀態: 停止服務");
        }
    }
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="scheduleService.plugin" type="ScheduleService.Configuration.Plugin, ServicePlugin"/>
    </configSections>
    <scheduleService.plugin>
        <monitorPort value="9876" />
        <!--<exceptionHandler binary="MyLib.dll" assembly="MyLib.MyHandler" />-->
        <plugins>
            <!--<module name="PluginName" type="ScheduleService.Plugin.IGeneralPlugin" binary="MyLib.dll" assembly="MyLib.ClassName" />-->
            <!--<module name="PluginName" type="ScheduleService.Plugin.ISimplePlugin" binary="MyLib.dll" assembly="MyLib.ClassName" interval="10" reTryOnFailed="false" />-->
            <!--
                druation = Day | Week | Month
                executePoint = (tt:)HH:mm
            -->
            <!--<module name="PluginName" type="ScheduleService.Plugin.IAlarmPlugin" binary="MyLib.dll" assembly="MyLib.ClassName" duration="Month" executePoint="3:15:32" reTryOnFailed="false" />-->
        </plugins>
    </scheduleService.plugin>
</configuration>
Review of installation/uninstallation
%windir%\Microsoft.NET\Framework\v2.0.50727\installutil ServiceController.exe
%windir%\Microsoft.NET\Framework\v2.0.50727\installutil /u ServiceController.exe