Squirrel / Squirrel.Windows

An installation and update framework for Windows desktop apps
MIT License
7.38k stars 1.03k forks source link

No icon in the Add or Remove Programs window #761

Open jeremy-bridges opened 8 years ago

jeremy-bridges commented 8 years ago

A small polish thing, but it would be nice to see a proper icon under my application in the Windows Add or Remove Programs dialog. Should be as simple as creating the DisplayIcon registry key. I would imagine most folks would want this icon to be the same one as the desktop and start menu shortcuts. This StackOverflow post describes essentially what I'm looking for (but it is obviously for a different installer):

http://stackoverflow.com/questions/16204889/how-to-change-the-icon-in-add-or-remove-programs

Andrew-Hanlon commented 8 years ago

If you set the nuget package icon uri it does this already.

AnderssonPeter commented 8 years ago

@jeremy-bridges you can define the icon in your .nuspec file under package\metadata\iconUrl it must be on a public Webserver and its fetched at installation time not packaging time.

So don't remove the icon from the webserver after you created your package.

Also there seems to be some sort of tricky caching involved here so try it on a new computer if it doesn't work.

MattStypa commented 8 years ago

A related issue is when the app is installed when no internet is available. In that case, the icon will not be set.

It would be great if we could use embedded icon.

Mjinx commented 6 years ago

This is an old issue, but I'm sure it remain problematic for some. I fix this issue by setting the DisplayIcon value of the Uninstall registry key during "--squirrel-install, --squirrel-updated" startup event.

The value can be path to an icon of your choice or just set it to the application executable path this uses the embedded app icon. In my case I just bundle the icon with my app and just copy it to installation root to mimic the normal squirrel behavior which downloads the nuget to local machine.

Hope this helps and maybe become an option in future releases.

cosminsg commented 4 years ago

Has anyone managed to do this on an offline environment? @Mjinx , are you talking about the Custom Squirrel Events? (https://github.com/Squirrel/Squirrel.Windows/blob/develop/docs/using/custom-squirrel-events.md). Could you post a sample code?

Mjinx commented 4 years ago

My solution was offline on an electron app and Yes using the builtin events.

I don't have access to the code but basically I listen to first run event ("--squirrel-firstrun") and updated the uninstall registry value icon ("\Software\Microsoft\Windows\CurrentVersion\Uninstall{{APPNAME}}\DisplayIcon") with the icon that I shipped with my product. (you can also point the path to your .exe it will use display the icon as is on the file i believe, but needs testing.)

ahmedayman4a commented 4 years ago

I have tried this C# code but for some reason the key always returns null :

// under static void main:
using (var mgr = new UpdateManager(@"AppLocation"))
{
    SquirrelAwareApp.HandleEvents(
      onInitialInstall: v => mgr.CreateShortcutForThisExe(),
      onFirstRun: () => AddIconToRegistry(),
      onAppUpdate: v => mgr.CreateShortcutForThisExe(),
      onAppUninstall: v => mgr.RemoveShortcutForThisExe());
}

 private static void AddIconToRegistry()
 {
     Log.Information("Setting key value for icon in registry");
     using(RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser,RegistryView.Registry64))
     using (RegistryKey myKey = baseKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\AppName", true))
     {
         Log.Information("Current exe path : " + Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///",""));
         if (myKey is null)
         {
             Log.Warning("Key not found");
         }
         else
         {
             myKey.SetValue("DisplayIcon", Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///", ""), RegistryValueKind.String);
             Log.Information("Key value for icon is set successfully");
         }
     }
 }

Any help is appreciated

gkralik commented 4 years ago

Use CreateSubKey instead of OpenSubKey to create the missing key. It looks like the key is not yet created when the method is executed. Also you might want to consider setting the uninstaller icon in onInitialInstall and onAppUpdate (if you only use onInitialInstall the path to the executable might become invalid after installing a few updates).

Alonzzzo2 commented 4 years ago

Using the squirrel events is a great idea but the real solution would be to stop using the deprecated 'iconUrl' nuget attribute and start using the 'icon' nuget attribute This requires a small change to the squirrel code

vidia commented 4 years ago

It looks like this would be a simple change in CreateUninstallerRegistryEntry in Squirrel.Windows\src\Squirrel\UpdateManager.InstallHelpers.cs to check the <icon> if an <iconUrl> isn't provided (for the sake of compatibility).

It looks like a straightforward enough fix. The first nuance I see is that it converts to an ico (from png or ico) but the <icon> option only supports png and jpeg. But that code is already written to bail out aggressively in case of an error, so even partial support for an <icon> seems valuable.

Alonzzzo2 commented 3 years ago

Here's a part of the fix: In function CreateUninstallerRegistryEntry: `if (!string.IsNullOrEmpty(zp.Icon)) { try { var appDirName = $"app-{latest.Version}"; var iconFileInfo = new FileInfo(zp.Icon); var iconFileName = iconFileInfo.Name; var iconFullPath = Path.Combine(rootAppDirectory, appDirName, iconFileName); key.SetValue("DisplayIcon", iconFullPath, RegistryValueKind.String); this.Log().Info("Setting display icon"); } catch (Exception ex) { this.Log().InfoException("Couldn't write uninstall icon, don't care", ex); } } else { var targetPng = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".png"); var targetIco = Path.Combine(rootAppDirectory, "app.ico");

                if (zp.IconUrl != null && !File.Exists(targetIco)) {
                    try {
                        using (var wc = Utility.CreateWebClient()) {
                            await wc.DownloadFileTaskAsync(zp.IconUrl, targetPng);
                            using (var fs = new FileStream(targetIco, FileMode.Create)) {
                                if (zp.IconUrl.AbsolutePath.EndsWith("ico")) {
                                    var bytes = File.ReadAllBytes(targetPng);
                                    fs.Write(bytes, 0, bytes.Length);
                                } else {
                                    using (var bmp = (Bitmap)Image.FromFile(targetPng))
                                    using (var ico = Icon.FromHandle(bmp.GetHicon())) {
                                        ico.Save(fs);
                                    }
                                }

                                key.SetValue("DisplayIcon", targetIco, RegistryValueKind.String);
                            }
                        }
                    } catch(Exception ex) {
                        this.Log().InfoException("Couldn't write uninstall icon, don't care", ex);
                    } finally {
                        File.Delete(targetPng);
                    }
                }
            }`

But this requires further changes to the LocalPackage.cs file and some more, in order to introduce the new Icon property.

I want to create a PR but not authorized to do so

ken-sands commented 3 years ago

So, if you don't have internet access to an icon hosted on the web somewhere during install this fails? Sorry but that is insanity, why on earth isn't the default just an icon included with the setup msi or exe (or even the icon used for the setup.exe) I suppose the comment // Download the icon and PNG => ICO it. If this doesn't work, who cares sums up the approach. The answer is people looking to create a professional product that works and doesn't look like it's broken just because a person installs it while offline. Heck half the systems we might roll out to in an organisation would likely be intranet only. Not to mention the install only really works while a website is up and running factor. I genuinely find it hard to understand anyone choosing to make it this way, is it a perhaps a consequence of using some of the other packages rather than by design?

Alonzzzo2 commented 3 years ago

@ken-sands I've implemented a fix for this issue and recompiled squirrel, locally ( along with some other modifications I had to do which will suit our needs). If you would like I could help you do the same if needed, otherwise, my fix is posted in a comment I previously posted in this thread

ken-sands commented 3 years ago

@Alonzzzo2 That's really kind and helpful thanks, I've just done the same thing but if I find anything acting weird I may well pick your brain.

Alonzzzo2 commented 3 years ago

Sure, with pleasure

javierguzman commented 1 year ago

I do not really understand how this is still not solved since 2016 and there is even a solution posted by @Alonzzzo2 that could be merge. Am I missing something??

anaisbetts commented 1 year ago

@javierguzman Looking forward to your PR!

liu-jin-yi commented 4 months ago

I solved this problem in electron forge.

Follow the code in https://github.com/mongodb-js/electron-squirrel-startup/blob/fcb9674bb9fce2c0241e75b2f57a9b70fa2b6c52/index.js#L1

Just pass "--icon=xxxxxxx"

For example: run(["--createShortcut=" + target + "", "--icon=" + iconPath + ""],app.quit );