feat(app): 初始化自动关机工具首个版本
实现基于 WPF 的 AutoShutdown 主界面,支持关机、重启、睡眠、休眠、唤醒、锁屏和注销等电源任务。 支持指定时间和倒计时计划、执行前提醒、系统关机撤销、Windows 唤醒任务、托盘运行、自定义图标以及 OmniNotify 通知适配。 修复关闭到托盘时的运行提醒,并支持单击托盘图标打开或收起主界面。 补充 README、发布配置和 win-x64 Release 输出要求。 Release: win-x64
This commit is contained in:
119
Services/WakeService.cs
Normal file
119
Services/WakeService.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
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 删除失败不影响计划本身。
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user