C# 中的自动关机服务





5.00/5 (16投票s)
这是“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 - 添加了更多状态选项和配置窗体