skip to Main Content

Question:

Why does my unpackaged C# application show its icon when I launch it but not when my Windows service launches it? How can I make my app’s toasts always show the app’s icon?

Details:

I have a C++ Windows Service that launches a C# Win32 application for toast functionality, since toasts cannot be launched directly from a service to a user. It is an absolute requirement that the service launches the toast app. To my frustration, however, the app’s icon (i.e. the icon shown on the .exe in Explorer) refuses to show only when launched by my service. Here is an example of what I see when my service launches the app (Note the three squares. This is the Windows 10 default icon):

Toast from app instance launched by my service

When I manually launch the app (i.e. click it), this is what I see instead:

Toast from app instance I launched

The only difference between the above two screenshots is the launch method. The most succinct way I can describe my issue is that I want the launch method (launched from a service) that yields the first screenshot to yield the second screenshot instead.

I can provide the code snippet I used to generate these toasts, although I doubt its usefulness for finding a solution:

var notifier = ToastNotificationManagerCompat.CreateToastNotifier();

var xml = new Windows.Data.Xml.Dom.XmlDocument();
xml.LoadXml("<toast><visual><binding template="ToastGeneric"><text>Foo</text<text>Bar</text></binding></visual></toast>");

var notif = new Windows.UI.Notifications.ToastNotification(xml);

notifier.Show(notif);

The most useful code sample I believe I can provide is the code that the service uses to launch the app:

void SpawnToastApp()
{
    constexpr int       nProcFlags  = DETACHED_PROCESS | NORMAL_PRIORITY_CLASS;
    constexpr wchar_t*  wcsDesktop  = L"WinSta0\Default";
    constexpr wchar_t*  wcsToastApp = L"ToastApp\Toast App (WIP).exe";
    HANDLE              hUser       = NULL;
    STARTUPINFOW        si{ 0 };
    wchar_t             wcsCmdLine[MAX_PATH]{ 0 };

    _snwprintf_s(wcsCmdLine, _TRUNCATE, L""%S\%s" %lu", _strInstallDir, wcsToastApp, GetCurrentProcessId());

    _sessionCanToast = WTSQueryUserToken(_sessionId, &hUser);
    if (_sessionCanToast)
    {
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        si.lpDesktop = wcsDesktop;

        _sessionCanToast = CreateProcessAsUserW(hUser, NULL, wcsCmdLine, NULL, NULL, FALSE,
            nProcFlags, NULL, NULL,
            &si, &_toastHandlerProcessInformation);
    }
    
    if(!_sessionCanToast)
    {
        /// Log it
    }

    if (hUser) { CloseHandle(hUser); };
}

I include the C++ code because I believe that I have narrowed the problem down to the launch method but am unsure the specific cause beyond that.

Additional Information:

These screenshots utilize Windows.UI.Notifications.ToastNotifications created from raw XML, but I have also tried using the Microsoft.Toolkit.Uwp.Notifications NuGet Package as recommended by Microsoft to the same effect.

I believe this project is a Windows Form App.

I am not using any sort of package–no APPX, MSIX, or sparse package. This is meant to be a lightweight app whose sole function is for toasts. While using a package isn’t out of the question, suffice it to say that the number of hurdles and implementation issues make packaging this app undesirable. Indeed, the only reason I would want to package this app is for the icon in the upper left-hand corner of it, which it evidently does already, just not in the way I desire.

Similar to but NOT a duplicate of:

EDIT 1:
I followed a sparse packaging guide found here to more or less the same result, the main difference being that now no icon not shows up at all anywhere. I used the asset generator in Visual Studio and then used the MSIX unpackaging tool to inspect the contents of the sparse package and confirmed it contained the generated assets. I had to comment out the reference to the splash screen because the app failed to register with that line included in the manifest.

EDIT 2:
I have decided to proceed with this app as if I am not having this issue, and so I used Visual Studio’s Performance Profiler to analyze my app’s resources. The Performance Profiler launched my toast app, and the toasts had the correct icon, so at this point I am 100% certain it has something to do with my service’s launch method. Unfortunately, I am no closer to understanding why the icon does not show only when launched from my service.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to @Jonah Bui's answer, I believe there is a solution to this issue for C# implementations, although I am not 100% sure because I will not be testing it myself. However, the solution he describes perfectly mirrors my own, so I have no doubt it would work.

    The solution I ultimately started using was to re-write the app in C++. I recognize this is somewhat of an anti-solution given the question, which is why I elected to accept Jonah's answer instead.

    I followed Microsoft's guide on sending toasts from an unpackaged C++ application, which links to this github repository. Despite the guide being for WRL, the directory in the repository that was most useful for me was the WINRT directory because shows an implementation that does not use a package for its identity.

    Incorporating the DesktopNotificationManagerCompat.h and DesktopNotificationManagerCompat.cpp allows a C++ application to give itself identity for toast notifications by calling DesktopNotificationManagerCompat::Register(); at the start of main(). Note that using WINRT requires using C++17 for the compiler's (e.g. Visual Studio) language standard. I also seem to remember Windows 10 build 17763 being the minimum version that supports unpackaged apps sending toasts, but I could be misremembering because I cannot find a source for this information.

    Here is the Register() function:

    void DesktopNotificationManagerCompat::Register(std::wstring aumid, std::wstring displayName, std::wstring iconPath)
    {
        // If has identity
        if (HasIdentity())
        {
            // No need to do anything additional, already registered through manifest
            return;
        }
    
        _win32Aumid = aumid;
    
        std::wstring clsidStr = CreateAndRegisterActivator();
    
        // Register via registry
        std::wstring subKey = LR"(SOFTWAREClassesAppUserModelId)" + _win32Aumid;
    
        // Set the display name and icon uri
        SetRegistryKeyValue(HKEY_CURRENT_USER, subKey, L"DisplayName", displayName);
    
        if (!iconPath.empty())
        {
            SetRegistryKeyValue(HKEY_CURRENT_USER, subKey, L"IconUri", iconPath);
        }
        else
        {
            DeleteRegistryKeyValue(HKEY_CURRENT_USER, subKey, L"IconUri");
        }
    
        // Background color only appears in the settings page, format is
        // hex without leading #, like "FFDDDDDD"
        SetRegistryKeyValue(HKEY_CURRENT_USER, subKey, L"IconBackgroundColor", iconPath);
    
        SetRegistryKeyValue(HKEY_CURRENT_USER, subKey, L"CustomActivator", L"{" + clsidStr + L"}");
    }
    

    This function and Jonah's are identical in functionality. In fact, when I looked at the registry key that Jonah pointed to, I found the old AUMIDs from my C# implementation right alongside the AUMID for my current C++ app. None of the C# AUMIDs have an IconUri value, which is almost certainly indicates what the issue was all along. In my code, I would try to set the icon uri, but I never did it directly through the registry.


  2. So I’ve experienced a similar issue. I have not come across a way to fix the underlying issue, but I do have a remediation for it.

    When your application shows a toast notification an entry is created in Settings > Notifications & actions so that the user can choose to disable it. You can find each entry defined in the registry under ComputerHKEY_CLASSES_ROOTAppUserModelId. This article provides a different location in the registry, but I always found them under HKEY_CLASSES_ROOT.

    If you find the AppUserModelId key for your application, which should be auto-generated in your case, then you’ll notice it is missing the IconUri value. The quick fix is to do a one-time setup and set the IconUri data to the path to your icon (assuming you have it somewhere on disk).

    However, this fix leads to another question. How do you determine which AppUserModelId is for your notification application if it is auto-generated, so that you can access it in the registry? Iterating over all the registry subkeys is not ideal. It turns out you can manually set the AppUserModelId if you use the SetCurrentProcessExplicitAppUserModelId. I run this method at the beginning of my application.

    Below is a barebones C# implementation.

    [DllImport("shell32.dll")]
    internal static extern void SetCurrentProcessExplicitAppUserModelID(
      [MarshalAs(UnmanagedType.LPWStr)] string AppID);
    
    private static void Main(string[] args)
    {
      var key = Registry.ClassesRoot.OpenSubKey(@"AppUserModelIdMY_APPUSERMODELID");
      if (key == null) {
        key = Registry.ClassesRoot.CreateSubKey(@"AppUserModelIdMY_APPUSERMODELID");
      }
      if (key.GetValue("IconUri") == null) { 
        key.SetValue("IconUri", "path/to/your/icon");
      }
      // Show toast notification here
    }
    

    I don’t know if it is a full-proof solution, but it worked for me so far.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search