Files
ClaudeDo/src/ClaudeDo.Installer/Steps/CreateShortcutsStep.cs
CubeGameLP 78831b2263 feat(installer): add WPF installer/configurator project
Standalone WPF app (ClaudeDo.Installer) that handles full installation
and ongoing configuration of ClaudeDo. Two modes: wizard for first run,
tabbed settings panel for subsequent launches. Page-based extensibility
via IInstallerPage interface — adding new config sections requires only
one new class.

Install pipeline: dotnet publish, deploy binaries, write configs, init
DB (via SchemaInitializer from ClaudeDo.Data), register Windows Service,
create shortcuts. Dark theme matching the Avalonia app (forest teal accent).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 21:01:03 +02:00

92 lines
3.8 KiB
C#

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<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> 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
}