using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using ClaudeDo.Installer.Core; namespace ClaudeDo.Installer.Steps; public sealed class CreateShortcutsStep : IInstallStep { public string Name => "Create Shortcuts"; public Task ExecuteAsync(InstallContext ctx, IProgress progress, CancellationToken ct) { try { var appExe = Path.Combine(ctx.InstallDirectory, "app", "ClaudeDo.App.exe"); var workingDir = Path.Combine(ctx.InstallDirectory, "app"); // Start Menu shortcut var startMenuDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), "Programs"); Directory.CreateDirectory(startMenuDir); var startMenuPath = Path.Combine(startMenuDir, "ClaudeDo.lnk"); CreateShortcut(startMenuPath, appExe, workingDir, "ClaudeDo Task Manager"); progress.Report($"Created Start Menu shortcut: {startMenuPath}"); // Desktop shortcut (optional) if (ctx.CreateDesktopShortcut) { var desktopPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory), "ClaudeDo.lnk"); CreateShortcut(desktopPath, appExe, workingDir, "ClaudeDo Task Manager"); progress.Report($"Created Desktop shortcut: {desktopPath}"); } return Task.FromResult(StepResult.Ok()); } catch (Exception ex) { return Task.FromResult(StepResult.Fail(ex.Message)); } } private static void CreateShortcut(string shortcutPath, string targetPath, string workingDir, string description) { var link = (IShellLink)new ShellLink(); link.SetPath(targetPath); link.SetWorkingDirectory(workingDir); link.SetDescription(description); link.SetIconLocation(targetPath, 0); var file = (IPersistFile)link; file.Save(shortcutPath, false); } #region COM Interop for IShellLink [ComImport] [Guid("00021401-0000-0000-C000-000000000046")] private class ShellLink { } [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("000214F9-0000-0000-C000-000000000046")] private interface IShellLink { void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, int fFlags); void GetIDList(out IntPtr ppidl); void SetIDList(IntPtr pidl); void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); void GetHotkey(out short pwHotkey); void SetHotkey(short wHotkey); void GetShowCmd(out int piShowCmd); void SetShowCmd(int iShowCmd); void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon); void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); void Resolve(IntPtr hwnd, int fFlags); void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); } #endregion }