From a68952653c0c84abe6d83cb4fc2e888a5376d739 Mon Sep 17 00:00:00 2001 From: Nathan McRae Date: Wed, 22 Jan 2025 22:45:30 -0800 Subject: [PATCH] Start implementing task scheduler --- .../MainSettingsController.java | 2 +- .../numbersstation/WindowsScheduler.java | 238 ++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 src/main/java/name/nathanmcrae/numbersstation/WindowsScheduler.java diff --git a/src/main/java/name/nathanmcrae/numbersstation/MainSettingsController.java b/src/main/java/name/nathanmcrae/numbersstation/MainSettingsController.java index 3ec730b..562401c 100644 --- a/src/main/java/name/nathanmcrae/numbersstation/MainSettingsController.java +++ b/src/main/java/name/nathanmcrae/numbersstation/MainSettingsController.java @@ -397,7 +397,7 @@ public class MainSettingsController { @FXML private void handleTestConnectionButtonPress() { - // TODO + WindowsScheduler.registerSchedule(settings); } public StringProperty stationAddressProperty() { diff --git a/src/main/java/name/nathanmcrae/numbersstation/WindowsScheduler.java b/src/main/java/name/nathanmcrae/numbersstation/WindowsScheduler.java new file mode 100644 index 0000000..2099f66 --- /dev/null +++ b/src/main/java/name/nathanmcrae/numbersstation/WindowsScheduler.java @@ -0,0 +1,238 @@ +package name.nathanmcrae.numbersstation; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class WindowsScheduler { + private static final Logger logger = Logger.getLogger(Main.class.getName()); + + public static Document generateScheduleDocument(String authorName, String userId) throws ParserConfigurationException { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + + // Root element + Document doc = docBuilder.newDocument(); + Element rootElement = doc.createElement("Task"); + rootElement.setAttribute("version", "1.2"); + rootElement.setAttribute("xmlns", "http://schemas.microsoft.com/windows/2004/02/mit/task"); + doc.appendChild(rootElement); + + // RegistrationInfo element + Element registrationInfo = doc.createElement("RegistrationInfo"); + rootElement.appendChild(registrationInfo); + + Element date = doc.createElement("Date"); + date.appendChild(doc.createTextNode("2025-01-22T22:09:01.6765321")); + registrationInfo.appendChild(date); + + Element author = doc.createElement("Author"); + author.appendChild(doc.createTextNode(authorName)); + registrationInfo.appendChild(author); + + Element uri = doc.createElement("URI"); + uri.appendChild(doc.createTextNode("\\test task")); + registrationInfo.appendChild(uri); + + // Triggers element + Element triggers = doc.createElement("Triggers"); + rootElement.appendChild(triggers); + + Element calendarTrigger = doc.createElement("CalendarTrigger"); + triggers.appendChild(calendarTrigger); + + Element startBoundary = doc.createElement("StartBoundary"); + startBoundary.appendChild(doc.createTextNode("2025-01-22T22:08:06")); + calendarTrigger.appendChild(startBoundary); + + Element enabled = doc.createElement("Enabled"); + enabled.appendChild(doc.createTextNode("true")); + calendarTrigger.appendChild(enabled); + + Element randomDelay = doc.createElement("RandomDelay"); + randomDelay.appendChild(doc.createTextNode("PT1H")); + calendarTrigger.appendChild(randomDelay); + + Element scheduleByDay = doc.createElement("ScheduleByDay"); + calendarTrigger.appendChild(scheduleByDay); + + Element daysInterval = doc.createElement("DaysInterval"); + daysInterval.appendChild(doc.createTextNode("1")); + scheduleByDay.appendChild(daysInterval); + + // Principals element + Element principals = doc.createElement("Principals"); + rootElement.appendChild(principals); + + Element principal = doc.createElement("Principal"); + principal.setAttribute("id", "Author"); + principals.appendChild(principal); + + Element userIdElement = doc.createElement("UserId"); + userIdElement.appendChild(doc.createTextNode(userId)); + principal.appendChild(userIdElement); + + Element logonType = doc.createElement("LogonType"); + logonType.appendChild(doc.createTextNode("InteractiveToken")); + principal.appendChild(logonType); + + Element runLevel = doc.createElement("RunLevel"); + runLevel.appendChild(doc.createTextNode("LeastPrivilege")); + principal.appendChild(runLevel); + + // Settings element + Element settings = doc.createElement("Settings"); + rootElement.appendChild(settings); + + Element multipleInstancesPolicy = doc.createElement("MultipleInstancesPolicy"); + multipleInstancesPolicy.appendChild(doc.createTextNode("IgnoreNew")); + settings.appendChild(multipleInstancesPolicy); + + Element disallowStartIfOnBatteries = doc.createElement("DisallowStartIfOnBatteries"); + disallowStartIfOnBatteries.appendChild(doc.createTextNode("true")); + settings.appendChild(disallowStartIfOnBatteries); + + Element stopIfGoingOnBatteries = doc.createElement("StopIfGoingOnBatteries"); + stopIfGoingOnBatteries.appendChild(doc.createTextNode("true")); + settings.appendChild(stopIfGoingOnBatteries); + + Element allowHardTerminate = doc.createElement("AllowHardTerminate"); + allowHardTerminate.appendChild(doc.createTextNode("true")); + settings.appendChild(allowHardTerminate); + + Element startWhenAvailable = doc.createElement("StartWhenAvailable"); + startWhenAvailable.appendChild(doc.createTextNode("false")); + settings.appendChild(startWhenAvailable); + + Element runOnlyIfNetworkAvailable = doc.createElement("RunOnlyIfNetworkAvailable"); + runOnlyIfNetworkAvailable.appendChild(doc.createTextNode("false")); + settings.appendChild(runOnlyIfNetworkAvailable); + + Element idleSettings = doc.createElement("IdleSettings"); + settings.appendChild(idleSettings); + + Element stopOnIdleEnd = doc.createElement("StopOnIdleEnd"); + stopOnIdleEnd.appendChild(doc.createTextNode("true")); + idleSettings.appendChild(stopOnIdleEnd); + + Element restartOnIdle = doc.createElement("RestartOnIdle"); + restartOnIdle.appendChild(doc.createTextNode("false")); + idleSettings.appendChild(restartOnIdle); + + Element allowStartOnDemand = doc.createElement("AllowStartOnDemand"); + allowStartOnDemand.appendChild(doc.createTextNode("true")); + settings.appendChild(allowStartOnDemand); + + Element enabledSetting = doc.createElement("Enabled"); + enabledSetting.appendChild(doc.createTextNode("true")); + settings.appendChild(enabledSetting); + + Element hidden = doc.createElement("Hidden"); + hidden.appendChild(doc.createTextNode("false")); + settings.appendChild(hidden); + + Element runOnlyIfIdle = doc.createElement("RunOnlyIfIdle"); + runOnlyIfIdle.appendChild(doc.createTextNode("false")); + settings.appendChild(runOnlyIfIdle); + + Element wakeToRun = doc.createElement("WakeToRun"); + wakeToRun.appendChild(doc.createTextNode("false")); + settings.appendChild(wakeToRun); + + Element executionTimeLimit = doc.createElement("ExecutionTimeLimit"); + executionTimeLimit.appendChild(doc.createTextNode("PT72H")); + settings.appendChild(executionTimeLimit); + + Element priority = doc.createElement("Priority"); + priority.appendChild(doc.createTextNode("7")); + settings.appendChild(priority); + + // Actions element + Element actions = doc.createElement("Actions"); + actions.setAttribute("Context", "Author"); + rootElement.appendChild(actions); + + Element exec = doc.createElement("Exec"); + actions.appendChild(exec); + + Element command = doc.createElement("Command"); + command.appendChild(doc.createTextNode("notepad.exe")); + exec.appendChild(command); + + return doc; + } + + public static String getUserId() throws IOException, InterruptedException { + Process process = new ProcessBuilder("whoami", "/user").start(); + process.waitFor(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("S-")) { + return line.split("\\s+")[1]; + } + } + } + return null; + } + + public static void registerSchedule(StationSettings settings) { + Path tempFile = null; + try { + String taskName = "numbers-station-main_" + settings.getName(); + + Document doc = generateScheduleDocument(System.getProperty("user.name"), getUserId()); + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + DOMSource source = new DOMSource(doc); + + tempFile = Files.createTempFile("task", ".xml"); + StreamResult result = new StreamResult(tempFile.toFile()); + + try (BufferedWriter writer = Files.newBufferedWriter(tempFile)) { + writer.write("\n"); + transformer.transform(source, new StreamResult(writer)); + } + + Process process = new ProcessBuilder("schtasks.exe", "/create", "/tn", taskName, "/xml", tempFile.toString()).start(); + int exitCode = process.waitFor(); + + if (exitCode == 0) { + logger.info(taskName + " task registered successfully."); + } else { + logger.info("Failed to register " + taskName + " task. Exit code: " + exitCode); + } + } catch (ParserConfigurationException | TransformerException | IOException | InterruptedException e) { + logger.log(Level.SEVERE, "Exception while registering schedule", e); + } finally { + if (tempFile != null) { + try { + Files.delete(tempFile); + } catch (IOException e) { + logger.log(Level.SEVERE, "Failed to delete temporary file", e); + } + } + } + } +}