feat(installer): register autostart via Startup shortcut, drop scheduled task
Replaces schtasks /Create with AutostartShortcut.Install; migrates away legacy scheduled task and Windows service on upgrade. Removes ScheduledTaskXml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,52 +0,0 @@
|
|||||||
using System.Security;
|
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Core;
|
|
||||||
|
|
||||||
public static class ScheduledTaskXml
|
|
||||||
{
|
|
||||||
public static string Build(string userId, string workerExePath, int restartIntervalMinutes)
|
|
||||||
{
|
|
||||||
var minutes = restartIntervalMinutes < 1 ? 1 : restartIntervalMinutes;
|
|
||||||
var user = SecurityElement.Escape(userId);
|
|
||||||
var cmd = SecurityElement.Escape(workerExePath);
|
|
||||||
return $"""
|
|
||||||
<?xml version="1.0" encoding="UTF-16"?>
|
|
||||||
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
||||||
<RegistrationInfo>
|
|
||||||
<Description>ClaudeDo background worker (per-user).</Description>
|
|
||||||
</RegistrationInfo>
|
|
||||||
<Triggers>
|
|
||||||
<LogonTrigger>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<UserId>{user}</UserId>
|
|
||||||
</LogonTrigger>
|
|
||||||
</Triggers>
|
|
||||||
<Principals>
|
|
||||||
<Principal id="Author">
|
|
||||||
<UserId>{user}</UserId>
|
|
||||||
<LogonType>InteractiveToken</LogonType>
|
|
||||||
<RunLevel>LeastPrivilege</RunLevel>
|
|
||||||
</Principal>
|
|
||||||
</Principals>
|
|
||||||
<Settings>
|
|
||||||
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
||||||
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
||||||
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
||||||
<AllowHardTerminate>true</AllowHardTerminate>
|
|
||||||
<StartWhenAvailable>true</StartWhenAvailable>
|
|
||||||
<Hidden>true</Hidden>
|
|
||||||
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
||||||
<RestartOnFailure>
|
|
||||||
<Interval>PT{minutes}M</Interval>
|
|
||||||
<Count>3</Count>
|
|
||||||
</RestartOnFailure>
|
|
||||||
</Settings>
|
|
||||||
<Actions Context="Author">
|
|
||||||
<Exec>
|
|
||||||
<Command>{cmd}</Command>
|
|
||||||
</Exec>
|
|
||||||
</Actions>
|
|
||||||
</Task>
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Principal;
|
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Steps;
|
namespace ClaudeDo.Installer.Steps;
|
||||||
|
|
||||||
public sealed class RegisterAutostartStep : IInstallStep
|
public sealed class RegisterAutostartStep : IInstallStep
|
||||||
{
|
{
|
||||||
public const string TaskName = "ClaudeDoWorker";
|
public const string LegacyTaskName = "ClaudeDoWorker";
|
||||||
private const string LegacyServiceName = "ClaudeDoWorker";
|
private const string LegacyServiceName = "ClaudeDoWorker";
|
||||||
|
|
||||||
public string Name => "Register Autostart";
|
public string Name => "Register Autostart";
|
||||||
@@ -34,24 +33,19 @@ public sealed class RegisterAutostartStep : IInstallStep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Register (or replace) the per-user logon task.
|
// 2) Migrate away the legacy logon scheduled task if present (best-effort).
|
||||||
var userId = WindowsIdentity.GetCurrent().Name;
|
progress.Report("Removing legacy logon task...");
|
||||||
var minutes = Math.Max(1, ctx.RestartDelayMs / 60000);
|
await ProcessRunner.RunAsync("schtasks.exe", $"/Delete /TN \"{LegacyTaskName}\" /F", null, progress, ct);
|
||||||
var xml = ScheduledTaskXml.Build(userId, workerExe, minutes);
|
|
||||||
|
|
||||||
var xmlPath = Path.Combine(Path.GetTempPath(), $"ClaudeDoWorker-{Guid.NewGuid():N}.xml");
|
// 3) Register per-user autostart via a Startup-folder shortcut.
|
||||||
await File.WriteAllTextAsync(xmlPath, xml, new System.Text.UnicodeEncoding(false, true), ct);
|
progress.Report("Creating Startup shortcut...");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
progress.Report("Registering logon task...");
|
AutostartShortcut.Install(AutostartShortcut.DefaultStartupDir, workerExe);
|
||||||
var (exit, output) = await ProcessRunner.RunAsync(
|
|
||||||
"schtasks.exe", $"/Create /TN \"{TaskName}\" /XML \"{xmlPath}\" /F", null, progress, ct);
|
|
||||||
if (exit != 0)
|
|
||||||
return StepResult.Fail($"schtasks /Create failed (exit {exit}): {output}");
|
|
||||||
}
|
}
|
||||||
finally
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
try { File.Delete(xmlPath); } catch { /* best effort */ }
|
return StepResult.Fail($"Failed to create Startup shortcut: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return StepResult.Ok();
|
return StepResult.Ok();
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
using ClaudeDo.Installer.Core;
|
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Tests;
|
|
||||||
|
|
||||||
public class ScheduledTaskXmlTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Build_EmbedsUserExeAndLogonTrigger()
|
|
||||||
{
|
|
||||||
var xml = ScheduledTaskXml.Build(
|
|
||||||
userId: "MACHINE\\mika",
|
|
||||||
workerExePath: @"C:\Program Files\ClaudeDo\worker\ClaudeDo.Worker.exe",
|
|
||||||
restartIntervalMinutes: 1);
|
|
||||||
|
|
||||||
Assert.Contains("<LogonTrigger>", xml);
|
|
||||||
Assert.Contains("<UserId>MACHINE\\mika</UserId>", xml);
|
|
||||||
Assert.Contains("<LogonType>InteractiveToken</LogonType>", xml);
|
|
||||||
Assert.Contains("<Hidden>true</Hidden>", xml);
|
|
||||||
Assert.Contains("<RunLevel>LeastPrivilege</RunLevel>", xml);
|
|
||||||
Assert.Contains(@"C:\Program Files\ClaudeDo\worker\ClaudeDo.Worker.exe", xml);
|
|
||||||
Assert.Contains("<Interval>PT1M</Interval>", xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Build_ClampsRestartIntervalToOneMinuteMinimum()
|
|
||||||
{
|
|
||||||
var xml = ScheduledTaskXml.Build("M\\u", @"C:\w.exe", restartIntervalMinutes: 0);
|
|
||||||
Assert.Contains("<Interval>PT1M</Interval>", xml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user