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:
mika kuns
2026-08-06 11:08:15 +02:00
parent cfd2936c25
commit 445242cd7d
5 changed files with 90 additions and 6 deletions
@@ -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 { }