实现基于 WPF 的 AutoShutdown 主界面,支持关机、重启、睡眠、休眠、唤醒、锁屏和注销等电源任务。 支持指定时间和倒计时计划、执行前提醒、系统关机撤销、Windows 唤醒任务、托盘运行、自定义图标以及 OmniNotify 通知适配。 修复关闭到托盘时的运行提醒,并支持单击托盘图标打开或收起主界面。 补充 README、发布配置和 win-x64 Release 输出要求。 Release: win-x64
120 lines
4.7 KiB
C#
120 lines
4.7 KiB
C#
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Security.Principal;
|
|
using System.Xml.Linq;
|
|
|
|
namespace AutoShutdown.Services;
|
|
|
|
internal sealed class WakeService
|
|
{
|
|
private const string TaskName = "AutoShutdown Wake";
|
|
|
|
public void ScheduleWake(DateTime wakeAt)
|
|
{
|
|
var taskXmlPath = Path.Combine(Path.GetTempPath(), $"AutoShutdown-Wake-{Guid.NewGuid():N}.xml");
|
|
|
|
try
|
|
{
|
|
File.WriteAllText(taskXmlPath, CreateWakeTaskXml(wakeAt));
|
|
RunProcess("schtasks.exe", $"/Create /TN \"{TaskName}\" /XML \"{taskXmlPath}\" /F");
|
|
}
|
|
finally
|
|
{
|
|
TryDelete(taskXmlPath);
|
|
}
|
|
}
|
|
|
|
public void CancelWake()
|
|
{
|
|
RunProcess("schtasks.exe", $"/Delete /TN \"{TaskName}\" /F", ignoreErrors: true);
|
|
}
|
|
|
|
private static string CreateWakeTaskXml(DateTime wakeAt)
|
|
{
|
|
XNamespace ns = "http://schemas.microsoft.com/windows/2004/02/mit/task";
|
|
var startBoundary = wakeAt.ToString("yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture);
|
|
var userId = WindowsIdentity.GetCurrent().User?.Value ?? Environment.UserName;
|
|
|
|
var task = new XDocument(
|
|
new XElement(ns + "Task",
|
|
new XAttribute("version", "1.4"),
|
|
new XElement(ns + "RegistrationInfo",
|
|
new XElement(ns + "Description", "AutoShutdown wake timer")),
|
|
new XElement(ns + "Triggers",
|
|
new XElement(ns + "TimeTrigger",
|
|
new XElement(ns + "StartBoundary", startBoundary),
|
|
new XElement(ns + "Enabled", "true"))),
|
|
new XElement(ns + "Principals",
|
|
new XElement(ns + "Principal",
|
|
new XAttribute("id", "Author"),
|
|
new XElement(ns + "UserId", userId),
|
|
new XElement(ns + "LogonType", "InteractiveToken"),
|
|
new XElement(ns + "RunLevel", "LeastPrivilege"))),
|
|
new XElement(ns + "Settings",
|
|
new XElement(ns + "MultipleInstancesPolicy", "IgnoreNew"),
|
|
new XElement(ns + "DisallowStartIfOnBatteries", "false"),
|
|
new XElement(ns + "StopIfGoingOnBatteries", "false"),
|
|
new XElement(ns + "AllowHardTerminate", "true"),
|
|
new XElement(ns + "StartWhenAvailable", "true"),
|
|
new XElement(ns + "RunOnlyIfNetworkAvailable", "false"),
|
|
new XElement(ns + "IdleSettings",
|
|
new XElement(ns + "StopOnIdleEnd", "false"),
|
|
new XElement(ns + "RestartOnIdle", "false")),
|
|
new XElement(ns + "AllowStartOnDemand", "true"),
|
|
new XElement(ns + "Enabled", "true"),
|
|
new XElement(ns + "Hidden", "true"),
|
|
new XElement(ns + "RunOnlyIfIdle", "false"),
|
|
new XElement(ns + "WakeToRun", "true"),
|
|
new XElement(ns + "ExecutionTimeLimit", "PT1M"),
|
|
new XElement(ns + "Priority", "7")),
|
|
new XElement(ns + "Actions",
|
|
new XAttribute("Context", "Author"),
|
|
new XElement(ns + "Exec",
|
|
new XElement(ns + "Command", "cmd.exe"),
|
|
new XElement(ns + "Arguments", "/c exit")))));
|
|
|
|
return task.ToString(SaveOptions.DisableFormatting);
|
|
}
|
|
|
|
private static void RunProcess(string fileName, string arguments, bool ignoreErrors = false)
|
|
{
|
|
using var process = Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = fileName,
|
|
Arguments = arguments,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true
|
|
});
|
|
|
|
if (process is null)
|
|
{
|
|
throw new InvalidOperationException("无法启动 Windows 任务计划程序命令。");
|
|
}
|
|
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
var error = process.StandardError.ReadToEnd();
|
|
process.WaitForExit();
|
|
|
|
if (!ignoreErrors && process.ExitCode != 0)
|
|
{
|
|
var message = string.Join(Environment.NewLine, new[] { error, output }.Where(item => !string.IsNullOrWhiteSpace(item)));
|
|
throw new InvalidOperationException(string.IsNullOrWhiteSpace(message) ? "创建唤醒计划失败。" : message.Trim());
|
|
}
|
|
}
|
|
|
|
private static void TryDelete(string path)
|
|
{
|
|
try
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
catch
|
|
{
|
|
// 临时任务 XML 删除失败不影响计划本身。
|
|
}
|
|
}
|
|
}
|