65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的自动关机服务

starIconstarIconstarIconstarIconstarIcon

5.00/5 (16投票s)

2014 年 1 月 7 日

CPOL

3分钟阅读

viewsIcon

48402

downloadIcon

2260

这是“AutoShut,我的第一个 C# 程序”的替代方案

引言

之前我写过

在这篇文章中,我将展示如何使用一个在后台运行的 Windows 服务来按计划自动关闭 PC(服务器)。通过一个 XML 配置文件,可以进行详细的配置。但现在,是...

在这篇文章中,我将展示如何按计划将您的 PC(服务器)置于另一种状态。我为程序添加了一些新选项。您现在可以

  • 注销用户
  • 强制注销用户
  • 重启 PC
  • 关机
  • 休眠 PC
  • 将 PC 置于睡眠模式

所有这些都可以按计划进行。您可以指定在哪个日期和时间更改 PC 的状态。

背景

我个人使用此程序每晚关闭我的服务器以节省一些电力成本。我使用该服务在特定时间关闭服务器,并使用 BIOS 的自动启动功能在早晨重新启动所有设备。

Using the Code

该服务非常直接,没有什么花哨的功能,只是一个简单的 Windows 服务,其中包含一个无限循环,每秒检查一次是否达到了具有特定配置集的特定时间点。所有配置都在一个 XML 文件中完成,该文件在 Windows 服务启动时或用户更改文件(例如使用记事本)时自动加载。为了让服务正常工作,我创建了一个 AutoShutdownWorker 类,如下所示。该类执行以下任务:

  • 检查在服务安装位置是否存在一个名为 AutoShutdownSchedules.xml 的文件。
  • 如果文件不存在,则创建一个示例文件,将一些信息写入 Windows 事件日志,然后关闭 Windows 服务。
  • 如果文件存在,则加载它;如果发生错误(例如 XML 文件损坏),则将一些错误信息写入 Windows 事件日志,然后关闭 Windows 服务。
  • 启动一个 filesystemwatcher 来检查配置文件是否被更改;当用户更改文件时,则重新加载此文件。
using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Threading;

namespace AutoShutdownService
{
    internal class AutoShutdownWorker
    {
        #region Fields
        /// <summary>
        /// A bool to keep our worker function running
        /// </summary>
        private volatile bool _runWorker;

        /// <summary>
        /// A list of shutdown schedules
        /// </summary>
        private AutoShutdownSchedules _autoShutdownSchedules;

        /// <summary>
        /// Used to watch if the AutoShutdownSchedules XML file has been changed
        /// </summary>
        private readonly FileSystemWatcher _fileSystemWatcher = new FileSystemWatcher();

        /// <summary>
        /// The name of the file that contains the schedule information
        /// </summary>
        private readonly string _scheduleFileName = 
                   new FileInfo(Assembly.GetExecutingAssembly().Location).Directory +
                                                    "\\AutoShutdownSchedules.xml";
        #endregion

        #region Start
        /// <summary>
        /// Start the worker function
        /// </summary>
        public void Start()
        {
            // When there is no AutoShutdownSchedules.xml we create an example file
            if (!File.Exists(_scheduleFileName))
            {
                try
                {
                    File.WriteAllText(_scheduleFileName, AutoShutdownSchedules.CreateExampleString());
                    EventLogging.WriteWarning("Could not find the file '" + _scheduleFileName +
                            "' an example file has been created. 
                            Please fill this file with your required reboot schedules");

                    // Stop the service
                    return;
                }
                catch (Exception e)
                {
                    EventLogging.WriteError(
                        "Tried to create an example 'AutoShutdownSchedules.xml' file in the folder '" +
                        Path.GetDirectoryName(_scheduleFileName) +
                        " but this failed, error: " + e.Message);
                }
            }

            // Otherwhise read the schedule file
            ReadAutoShutdownSchedulesFile();

            // Set the FileSystemWatcher so that we are notified 
            // when the AutoShutdownSchedules file changes
            _fileSystemWatcher.Path = Path.GetDirectoryName(_scheduleFileName);
            _fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
            _fileSystemWatcher.Changed += (source, e) => ReadAutoShutdownSchedulesFile();
            _fileSystemWatcher.Created += (source, e) => ReadAutoShutdownSchedulesFile();
            _fileSystemWatcher.EnableRaisingEvents = true;

            _runWorker = true;
            Worker();
        }
        #endregion

        #region Stop
        /// <summary>
        /// Stop the worker function
        /// </summary>
        public void Stop()
        {
            _runWorker = false;
        }
        #endregion

        #region ReadAutoShutdownSchedulesFile
        /// <summary>
        /// Read the autoshutdown schedules file if it exists
        /// </summary>
        private void ReadAutoShutdownSchedulesFile()
        {
            try
            {
                var xml = File.ReadAllText(_scheduleFileName);
                _autoShutdownSchedules = AutoShutdownSchedules.LoadFromString(xml);
            }
            catch (Exception e)
            {
                EventLogging.WriteError("Tried to read the file '" + 
                            _scheduleFileName + "' but an error happened, error: " + e.Message);
                Stop();
            }
        }
        #endregion

        #region Worker
        /// <summary>
        /// The shutdown worker function
        /// </summary>
        private void Worker()
        {
            while (_runWorker)
            {
                var weekDay = (int) DateTime.Now.DayOfWeek;

                var schedule = _autoShutdownSchedules.Schedules.Find(m => (int) m.Day == weekDay);

                if (schedule != null)
                {
                    if (DateTime.Now.ToString("HH:mm") == schedule.Time.ToString("HH:mm"))
                    {
                        var canClose = true;

                        // Check all the running processes to see if we can safely shutdown
                        var processes = Process.GetProcesses();

                        // Prevent ReSharper from annoying me that the foreach loop 
                        // can be converted to a LINQ expression.
                        // Yes ReSharper it can... but the code gets a lott more unreadable :-) 
                        // so stop bugging me
                        // ReSharper disable once LoopCanBeConvertedToQuery
                        foreach (var process in processes)
                        {
                            var process1 = process;
                            var result = _autoShutdownSchedules.Programs.Find
                                         (m => m.ToLower() == process1.ProcessName.ToLower());

                            if (result == null) continue;
                            canClose = false;
                            break;
                        }

                        // When we are on the correct day and time and there is not program 
                        // running that keeps us from
                        // shutting down then start the shutdown process.
                        if (canClose)
                        {
                            Stop();
                            ComputerState.SetState(schedule.ComputerState);
                        }
                    }
                }

                if(_runWorker)
                    Thread.Sleep(1000);
            }
        }
        #endregion
    }
}

AutoShutdownSchedules 类用于从 XML 文件加载配置(或写入示例文件)。XML 会被序列化为对象,以便我们可以在代码中非常简单地使用它。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;

namespace AutoShutdownService
{
    #region enum ScheduleWeekDays
    /// <summary>
    /// The weekdays
    /// </summary>
    public enum ScheduleWeekDays
    {
        Sunday,
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saterday,
    }
    #endregion

    [Serializable]
    public class AutoShutdownSchedules
    {
        #region Fields
        /// <summary>
        /// Programs that are not allowed to be active when we want to do a shutdown or reboot
        /// </summary>
        [XmlArray("Programs")]
        [XmlArrayItem("Program")]
        public List<string> Programs = new List<string>();

        /// <summary>
        /// The day and time that we want to shutdown
        /// </summary>
        [XmlArray("Schedules")]
        [XmlArrayItem("Schedule")]
        public List<schedule> Schedules = new List<schedule>();
        #endregion

        #region LoadFromString
        /// <summary>
        /// Create this object from an XML string
        /// </summary>
        /// <param name="xml" />The XML string
        public static AutoShutdownSchedules LoadFromString(string xml)
        {
            try
            {
                var xmlSerializer = new XmlSerializer(typeof(AutoShutdownSchedules));
                var rdr = new StringReader(xml);
                return (AutoShutdownSchedules) xmlSerializer.Deserialize(rdr);
            }
            catch (Exception e)
            {
                throw new Exception("The XML string contains invalid XML, error: " + e.Message);
            }
        }
        #endregion

        #region CreateExampleString
        /// <summary>
        /// Creates and example xml file as string
        /// </summary>
        public static string CreateExampleString()
        {
            var autoShowdownSchedules = new AutoShutdownSchedules();
            autoShowdownSchedules.Programs.Add("outlook");
            autoShowdownSchedules.Programs.Add("thunderbird");
            autoShowdownSchedules.Schedules.Add(new Schedule
            {
                ComputerState = ComputerStateType.Logoff,
                Day = ScheduleWeekDays.Monday,
                Time = DateTime.Parse("11:00")
            });

            autoShowdownSchedules.Schedules.Add(new Schedule
            {
                ComputerState = ComputerStateType.LogoffForced,
                Day = ScheduleWeekDays.Tuesday,
                Time = DateTime.Parse("11:00")
            });

            autoShowdownSchedules.Schedules.Add(new Schedule
            {
                ComputerState = ComputerStateType.Reboot,
                Day = ScheduleWeekDays.Wednesday,
                Time = DateTime.Parse("11:00")
            });

            autoShowdownSchedules.Schedules.Add(new Schedule
            {
                ComputerState = ComputerStateType.Shutdown,
                Day = ScheduleWeekDays.Thursday,
                Time = DateTime.Parse("11:00")
            });

            autoShowdownSchedules.Schedules.Add(new Schedule
            {
                ComputerState = ComputerStateType.Hibernate,
                Day = ScheduleWeekDays.Friday,
                Time = DateTime.Parse("11:00")
            });

            autoShowdownSchedules.Schedules.Add(new Schedule
            {
                ComputerState = ComputerStateType.Sleep,
                Day = ScheduleWeekDays.Saterday,
                Time = DateTime.Parse("11:00")
            });

            autoShowdownSchedules.Schedules.Add(new Schedule
            {
                ComputerState = ComputerStateType.Shutdown,
                Day = ScheduleWeekDays.Sunday,
                Time = DateTime.Parse("11:00")
            });

            return autoShowdownSchedules.SerializeToString();
        }

        #endregion

        #region SerializeToString
        /// <summary>
        /// Serialize this object to a string
        /// </summary>
        /// <returns>The object as an XML string</returns>
        public string SerializeToString()
        {
            var stringWriter = new StringWriter(new StringBuilder());
            var s = new XmlSerializer(GetType());

            s.Serialize(stringWriter, this);

            var output = stringWriter.ToString();
            output = output.Replace("", string.Empty);
            output = output.Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", string.Empty);
            output = output.Replace
                     ("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", string.Empty);

            return output;
        }
        #endregion
    }

    [Serializable]
    public class Schedule
    {
        #region Properties
        /// <summary>
        /// Set's the PC in a specific state, default is shutdown
        /// </summary>
        public ComputerStateType ComputerState { get; set; }

        /// <summary>
        /// Used to bind the ComputerStateType enum to a DataGridView
        /// </summary>
        [XmlIgnore]
        public string ComputerStateAsString { get 
                 { return Enum.GetName(typeof(ComputerStateType), ComputerState); } }
        
        /// <summary>
        /// The day of the week on which we want to do a shutdown or reboot
        /// </summary>
        public ScheduleWeekDays Day { get; set; }

        /// <summary>
        /// Used to bind the Day enum to a DataGridView
        /// </summary>
        [XmlIgnore]
        public string DayAsString { get { return Enum.GetName(typeof(ScheduleWeekDays), Day); } }

        /// <summary>
        /// The time on the day of the week on which we want to change the PC state
        /// </summary>
        public DateTime Time { get; set; }
        #endregion

        #region Constructor
        public Schedule()
        {
            // Default value for the computerstate type when nothing is set in the XML
            ComputerState = ComputerStateType.Shutdown;
        }
        #endregion
    }
}

在这个新版本中,创建了一个名为 ComputerState 的新类。通过这个类,您可以设置 PC 的状态。

#if (!DEBUG)
using System.Runtime.InteropServices;
#endif
using System.Windows.Forms;

namespace AutoShutdownService
{
    #region enum ComputerStateType
    public enum ComputerStateType
    {
        /// <summary>
        /// Logoff the user
        /// </summary>
        Logoff,

        /// <summary>
        /// Logoff the user and terminate any application that prevents it
        /// </summary>
        LogoffForced,

        /// <summary>
        /// Reboot the PC
        /// </summary>
        Reboot,

        /// <summary>
        /// Shutdown the PC
        /// </summary>
        Shutdown,

        /// <summary>
        /// Hibernate the PC
        /// </summary>
        Hibernate,

        /// <summary>
        /// Put the PC in sleep mode
        /// </summary>
        Sleep
    }
    #endregion

    /// <summary>
    /// With this class the state of the PC can be controlled.
    /// </summary>
    internal static class ComputerState
    {
        #region DllImports
#if (!DEBUG)
        [DllImport("user32.dll")]
        private static extern int ExitWindowsEx(int uFlags, int dwReason);
#endif
        #endregion

        #region SetState
        /// <summary>
        /// Puts the PC in a specific state
        /// </summary>
        /// <param name="computerState" />
        public static void SetState(ComputerStateType computerState)
        {
#if (DEBUG)
            switch (computerState)
            {
                case ComputerStateType.Logoff:
                    MessageBox.Show("PC would have logged of when we were not in DEBUG mode");
                    break;

                case ComputerStateType.LogoffForced:
                    MessageBox.Show
                      ("PC would have logged of with force when we were not in DEBUG mode");
                    break;

                case ComputerStateType.Reboot:
                    MessageBox.Show("PC would have been rebooted when we were not in DEBUG mode");
                    break;

                case ComputerStateType.Shutdown:
                    MessageBox.Show("PC would have shutdown when we were not in DEBUG mode");
                    break;

                case ComputerStateType.Hibernate:
                    MessageBox.Show("PC would have hibernated of when we were not in DEBUG mode");
                    break;

                case ComputerStateType.Sleep:
                    MessageBox.Show("PC would have gone to sleep when we were not in DEBUG mode");
                    break;
            }
#else
            switch (computerState)
            {
                case ComputerStateType.Logoff:
                    ExitWindowsEx(0, 0);
                    break;

                case ComputerStateType.LogoffForced:
                    ExitWindowsEx(4, 0);
                    break;

                case ComputerStateType.Reboot:
                    ExitWindowsEx(2, 0);
                    break;

                case ComputerStateType.Shutdown:
                    ExitWindowsEx(1, 0);
                    break;

                case ComputerStateType.Hibernate:
                    Application.SetSuspendState(PowerState.Hibernate, true, true);
                    break;

                case ComputerStateType.Sleep:
                    Application.SetSuspendState(PowerState.Suspend, true, true);
                    break;
            }
#endif
        }
        #endregion
    }
}

配置 XML

在之前的版本中,您需要自己使用记事本设置配置 XML。为了使一切都更轻松,已在 AutoShutdown Windows 服务中添加了一个窗体。当您启动 AutoShutdownService.exe 文件时,会显示一个窗体,您可以在其中进行必要的配置。

按下保存按钮后,此配置将被写入一个 XML 文件,AutoShutdown Windows 服务将检测到该文件的更改。配置文件看起来是这样的:

<autoshutdownschedules>
  <programs>
    <program>outlook</program>
    <program>thunderbird</program>
  </programs>
  <schedules>
    <schedule>
      <computerstate>Shutdown</computerstate>
      <day>Thursday</day>
      <time>2014-01-09T21:55:00</time>
    </schedule>
    <schedule>
      <computerstate>Hibernate</computerstate>
      <day>Tuesday</day>
      <time>2014-01-09T11:00:00</time>
    </schedule>
    <schedule>
      <computerstate>Reboot</computerstate>
      <day>Wednesday</day>
      <time>2014-01-09T21:43:00</time>
    </schedule>
  </schedules>
</autoshutdownschedules>

因为我们使用了 DateTimeField,所以在 XML 文件中也添加了一个日期,但该日期被程序忽略。

如何安装 Windows 服务

要使 AutoshutdownService 正常工作,您需要安装它。首先,请确保您已在 RELEASE 模式下编译了代码。然后,将 AutoShutdownService.exe 复制到 PC 上的某个文件夹中。例如,C:\Program Files\AutoshutdownService

启动命令提示符(以管理员身份运行)并键入以下命令:

InstallUtil autoshutdownservice.exe 

如果找不到 installutil 文件,可以从此处复制 --> c:\Windows\Microsoft.NET\Framework\v4.0.30319\

如果一切都已安装,只剩最后一件事。您需要启动该服务。可以使用以下命令完成:

net start autoshutdownservice

历史

  • 2014-01-07:第一个版本
  • 2014-01-09:版本 1.1 - 添加了更多状态选项和配置窗体
© . All rights reserved.