fix(installer): skip rewriting the autostart shortcut when already current
RegisterAutostartStep rewrote the Startup .lnk on every install/update/repair even when it already pointed at the right worker exe. AutostartShortcut.Install now reads the existing shortcut's target via ShortcutFactory.TryGetTarget and skips the rewrite when it matches, reporting the skip in progress output. Legacy service/scheduled-task cleanup stays unconditional (migration safety net).
This commit is contained in:
@@ -75,7 +75,7 @@ claude mcp add --transport http --scope user claudedo http://127.0.0.1:{External
|
||||
```
|
||||
Non-fatal if `claude` CLI is missing or too old (prints the manual command). Server name: `claudedo`.
|
||||
|
||||
**`RegisterAutostartStep`** — creates a per-user Startup-folder shortcut `ClaudeDo Worker.lnk` (`Environment.SpecialFolder.Startup`). Also migrates away from legacy mechanisms:
|
||||
**`RegisterAutostartStep`** — creates a per-user Startup-folder shortcut `ClaudeDo Worker.lnk` (`Environment.SpecialFolder.Startup`); `AutostartShortcut.Install` skips the rewrite (and reports it) when the shortcut already points at the current worker exe, so update/repair runs don't touch it needlessly. Also migrates away from legacy mechanisms, unconditionally on every run (no cached "already migrated" flag — see the comment in the step):
|
||||
- Deletes legacy Windows service: `sc.exe stop/delete ClaudeDoWorker`
|
||||
- Deletes legacy scheduled task: `schtasks /Delete /TN ClaudeDoWorker`
|
||||
|
||||
|
||||
@@ -11,13 +11,26 @@ public static class AutostartShortcut
|
||||
|
||||
public static string PathIn(string startupDir) => Path.Combine(startupDir, FileName);
|
||||
|
||||
public static void Install(string startupDir, string workerExe)
|
||||
/// <summary>Creates or updates the Startup shortcut. Returns false if it already pointed at <paramref name="workerExe"/> and was left untouched.</summary>
|
||||
public static bool Install(string startupDir, string workerExe)
|
||||
{
|
||||
Directory.CreateDirectory(startupDir);
|
||||
var shortcutPath = PathIn(startupDir);
|
||||
if (File.Exists(shortcutPath))
|
||||
{
|
||||
var existingTarget = ShortcutFactory.TryGetTarget(shortcutPath);
|
||||
if (existingTarget is not null && PathsEqual(existingTarget, workerExe))
|
||||
return false;
|
||||
}
|
||||
|
||||
var workingDir = Path.GetDirectoryName(workerExe) ?? startupDir;
|
||||
ShortcutFactory.CreateShortcut(PathIn(startupDir), workerExe, workingDir, "ClaudeDo background worker");
|
||||
ShortcutFactory.CreateShortcut(shortcutPath, workerExe, workingDir, "ClaudeDo background worker");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool PathsEqual(string a, string b) =>
|
||||
string.Equals(Path.GetFullPath(a), Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static void Remove(string startupDir)
|
||||
{
|
||||
var path = PathIn(startupDir);
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace ClaudeDo.Installer.Core;
|
||||
|
||||
public static class ShortcutFactory
|
||||
{
|
||||
private const int SlgpRawPath = 0x4;
|
||||
|
||||
public static void CreateShortcut(string shortcutPath, string targetPath, string workingDir, string description)
|
||||
{
|
||||
var link = (IShellLink)new ShellLink();
|
||||
@@ -18,6 +20,24 @@ public static class ShortcutFactory
|
||||
file.Save(shortcutPath, false);
|
||||
}
|
||||
|
||||
/// <summary>Reads the target path of an existing .lnk, or null if it can't be read (missing/corrupt).</summary>
|
||||
public static string? TryGetTarget(string shortcutPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var link = (IShellLink)new ShellLink();
|
||||
((IPersistFile)link).Load(shortcutPath, 0);
|
||||
var sb = new StringBuilder(260);
|
||||
link.GetPath(sb, sb.Capacity, IntPtr.Zero, SlgpRawPath);
|
||||
var path = sb.ToString();
|
||||
return path.Length == 0 ? null : path;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||
private class ShellLink { }
|
||||
|
||||
@@ -16,6 +16,11 @@ public sealed class RegisterAutostartStep : IInstallStep
|
||||
if (!File.Exists(workerExe))
|
||||
return StepResult.Fail($"Worker executable not found: {workerExe}");
|
||||
|
||||
// Legacy service/task cleanup below runs unconditionally on every install/update/repair,
|
||||
// even though it's a no-op once migrated. A cached "already migrated" flag could go stale
|
||||
// (e.g. a user re-adds the legacy service) and strand them with it still running; two
|
||||
// extra process starts per run is the price for that migration safety net.
|
||||
|
||||
// 1) Migrate away the legacy Windows service if present.
|
||||
progress.Report("Checking for legacy worker service...");
|
||||
var (queryExit, _) = await ProcessRunner.RunAsync("sc.exe", $"query {LegacyServiceName}", null, progress, ct);
|
||||
@@ -38,10 +43,11 @@ public sealed class RegisterAutostartStep : IInstallStep
|
||||
await ProcessRunner.RunAsync("schtasks.exe", $"/Delete /TN \"{LegacyTaskName}\" /F", null, progress, ct);
|
||||
|
||||
// 3) Register per-user autostart via a Startup-folder shortcut.
|
||||
progress.Report("Creating Startup shortcut...");
|
||||
progress.Report("Checking Startup shortcut...");
|
||||
try
|
||||
{
|
||||
AutostartShortcut.Install(AutostartShortcut.DefaultStartupDir, workerExe);
|
||||
var created = AutostartShortcut.Install(AutostartShortcut.DefaultStartupDir, workerExe);
|
||||
progress.Report(created ? "Startup shortcut created." : "Startup shortcut already up to date.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user