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):
When I manually launch the app (i.e. click it), this is what I see instead:
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:
-
Change toast notification icon in wpf
- I have already done this. My issue pertains to the icon’s inconsistency rather than the lack of it entirely.
-
Why is app icon missing for toast notifications in action center on desktop?
- I am using a Release build of my app
-
Cannot override notification app logo for Windows 10/11 VSTO app
- Using
AppOverrideLogo
gets my icon to show under all circumstances, but it’s more like a picture in the body of the toast rather than the small icon in the upper left-hand corner of the toast. Essentially, it’s not the style I want.
- Using
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
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
andDesktopNotificationManagerCompat.cpp
allows a C++ application to give itself identity for toast notifications by callingDesktopNotificationManagerCompat::Register();
at the start ofmain()
. 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: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.
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 underComputerHKEY_CLASSES_ROOTAppUserModelId
. This article provides a different location in the registry, but I always found them underHKEY_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 theIconUri
value. The quick fix is to do a one-time setup and set theIconUri
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 theAppUserModelId
if you use the SetCurrentProcessExplicitAppUserModelId. I run this method at the beginning of my application.Below is a barebones C# implementation.
I don’t know if it is a full-proof solution, but it worked for me so far.