From b10370f8bc095419bcd911f8520ee74d8663c471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ebbe=20Ba=C3=9F?= Date: Wed, 19 Aug 2026 10:30:41 +0200 Subject: [PATCH] added install wizard --- .claude/PROJECT-CONTEXT.md | 33 +++++++++- installer/EbbesMemeClipboard.iss | 108 +++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 installer/EbbesMemeClipboard.iss diff --git a/.claude/PROJECT-CONTEXT.md b/.claude/PROJECT-CONTEXT.md index d98a56d..ef39986 100644 --- a/.claude/PROJECT-CONTEXT.md +++ b/.claude/PROJECT-CONTEXT.md @@ -77,13 +77,44 @@ dotnet run --project src/EbbesMemeClipboard # build dotnet build -# publish self-contained single-file binary -> publish/EbbesMemeClipboard.exe (~78 MB) +# portable single exe -> publish/EbbesMemeClipboard.exe (~75 MB, compressed) dotnet publish src/EbbesMemeClipboard/EbbesMemeClipboard.csproj -c Release -r win-x64 \ --self-contained true -p:PublishSingleFile=true \ -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true \ -o publish + +# installer build: NOTE compression deliberately OFF (see below) -> publish-installer/ (~172 MB) +dotnet publish src/EbbesMemeClipboard/EbbesMemeClipboard.csproj -c Release -r win-x64 \ + --self-contained true -p:PublishSingleFile=true \ + -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=false \ + -o publish-installer + +# then compile the wizard -> installer/output/EbbesMemeClipboard-Setup-.exe (~51 MB) +"$LOCALAPPDATA/Programs/Inno Setup 6/ISCC.exe" installer/EbbesMemeClipboard.iss ``` +### Installer (Inno Setup) + +Script: `installer/EbbesMemeClipboard.iss` (tracked; `installer/output/` is gitignored). +Inno Setup 6 installs **per-user** to `%LocalAppData%\Programs\Inno Setup 6\`, not Program +Files — that tripped up the first attempt to locate `ISCC.exe`. + +- Per-user install by default (no UAC), with a wizard page letting the user pick + "just me" vs "all users" (`PrivilegesRequired=lowest` + + `PrivilegesRequiredOverridesAllowed=dialog`). +- Optional unchecked tasks: desktop icon, start-with-Windows. +- **Don't double-compress.** Publishing with `EnableCompressionInSingleFile=false` and + letting Inno's LZMA2 do the work gives a ~51 MB setup vs ~70 MB, and the installed app + starts faster because there's no decompression at launch. +- **Uninstall only removes the autostart entry if it points at the installed copy.** The + app's own Settings toggle writes the same `Run` value name, so an unconditional delete + would wipe out a portable build's autostart. This was a real bug caught in testing. +- User data under `%AppData%\EbbesMemeClipboard` is deliberately preserved on uninstall; + only the re-downloadable `GifCache` is removed. +- Verified end-to-end: silent install → files/shortcut/uninstall-entry present → app runs → + silent uninstall → everything removed, user data and unrelated autostart intact. +- An installer does **not** fix SmartScreen warnings; only code signing does. + Note: the published single-file exe takes **~10 seconds on first launch** before the tray icon appears (native library self-extraction to TEMP). Not a bug. Subsequent launches are faster. diff --git a/installer/EbbesMemeClipboard.iss b/installer/EbbesMemeClipboard.iss new file mode 100644 index 0000000..dd3d488 --- /dev/null +++ b/installer/EbbesMemeClipboard.iss @@ -0,0 +1,108 @@ +; Inno Setup script for Ebbe's Meme Clipboard. +; +; Build with: +; "%LocalAppData%\Programs\Inno Setup 6\ISCC.exe" installer\EbbesMemeClipboard.iss +; +; Expects an installer-flavoured publish first (note: compression DISABLED on purpose): +; dotnet publish src\EbbesMemeClipboard\EbbesMemeClipboard.csproj -c Release -r win-x64 ^ +; --self-contained true -p:PublishSingleFile=true ^ +; -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=false ^ +; -o publish-installer +; +; Leaving .NET's own single-file compression off lets Inno's LZMA2 compress the raw bytes +; instead, which it does far better: ~51 MB setup vs ~70 MB when double-compressing. The +; installed exe is larger on disk (172 MB vs 75 MB) but starts faster, since there's no +; decompression on launch. The separate publish\ folder keeps the compressed build for +; anyone who just wants the portable single exe. + +#define AppName "Ebbe's Meme Clipboard" +#define AppVersion "1.1.0" +#define AppPublisher "Ebbe Baß" +#define AppExeName "EbbesMemeClipboard.exe" +#define SourceExe "..\publish-installer\EbbesMemeClipboard.exe" + +[Setup] +AppId={{8E4C1F02-6B3A-4D77-9C21-5A0E7F3B9D64} +AppName={#AppName} +AppVersion={#AppVersion} +AppVerName={#AppName} {#AppVersion} +AppPublisher={#AppPublisher} +DefaultDirName={autopf}\Ebbes Meme Clipboard +DefaultGroupName={#AppName} +UninstallDisplayName={#AppName} +UninstallDisplayIcon={app}\{#AppExeName} +OutputDir=.\output +OutputBaseFilename=EbbesMemeClipboard-Setup-{#AppVersion} +SetupIconFile=..\src\EbbesMemeClipboard\Assets\tray-icon.ico +Compression=lzma2/max +SolidCompression=yes +WizardStyle=modern +DisableProgramGroupPage=yes +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible + +; Default to a per-user install so no UAC prompt is needed, but show a dialog letting the +; user pick "just me" or "all users". {autopf} then resolves to LocalAppData\Programs or +; Program Files to match whichever they chose. +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog + +; The app has no single-instance mutex, so a running copy would hold a lock on the exe and +; break an upgrade. Restart Manager closes it first and reopens it afterwards. +CloseApplications=yes +RestartApplications=yes + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "german"; MessagesFile: "compiler:Languages\German.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +Name: "autostart"; Description: "Start {#AppName} when Windows starts"; GroupDescription: "Startup:"; Flags: unchecked + +[Files] +Source: "{#SourceExe}"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] +Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}" +Name: "{group}\{cm:UninstallProgram,{#AppName}}"; Filename: "{uninstallexe}" +Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon + +[Registry] +; Same value name the app's own Settings toggle manages, so the installer checkbox and the +; in-app checkbox stay in agreement instead of fighting over two separate entries. +Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; \ + ValueName: "EbbesMemeClipboard"; ValueData: """{app}\{#AppExeName}"""; \ + Flags: uninsdeletevalue; Tasks: autostart + +[Run] +Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#AppName}}"; \ + Flags: nowait postinstall skipifsilent + +[UninstallDelete] +; The app writes its library/settings under %AppData% and a re-downloadable cache under +; %LocalAppData%. Only the cache is removed automatically - the user's own memes, favourites +; and settings are deliberately left behind so an uninstall/reinstall doesn't destroy them. +Type: filesandordirs; Name: "{localappdata}\EbbesMemeClipboard\GifCache" + +[Code] +// Clean up the autostart entry on uninstall, but ONLY when it actually points at the copy +// being removed. The app's own Settings toggle writes the same value name, so a user running +// a portable build alongside this one would otherwise have their autostart silently deleted +// by an uninstall that had nothing to do with it. +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + Existing: String; + AppPath: String; +begin + if CurUninstallStep <> usPostUninstall then + Exit; + + if not RegQueryStringValue(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Run', + 'EbbesMemeClipboard', Existing) then + Exit; + + AppPath := LowerCase(ExpandConstant('{app}')); + if Pos(AppPath, LowerCase(Existing)) > 0 then + RegDeleteValue(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Run', 'EbbesMemeClipboard'); +end;