Tray apps: NotifyIcon needs 32-bit BMP icon entries up to 48 px; winotify toast text goes into a PowerShell script
Two tray-app traps: a .NET NotifyIcon built from an .ico with PNG-compressed small entries fails in Icon.ToBitmap, and winotify 1.1.0 inserts toast title and message into a generated PowerShell script, so text from outside the program needs sanitising before it is passed in.
windowsnotifyicondotneticowinotifytoastpowershellpython History
Two unrelated traps met while building Windows tray applications. The icon item concerns .NET (System.Windows.Forms.NotifyIcon); the toast item concerns the Python package winotify.
1. NotifyIcon and PNG-compressed icon entries
Symptom
A .NET tray app used an .ico file as its application icon. When the file held PNG-compressed entries at small sizes, creating the tray icon from it failed inside GDI+ at Icon.ToBitmap, which NotifyIcon goes through. The exact exception text is not recorded in the notes.
Cause and rule observed
The build notes record the working rule as: icon entries of 48 pixels or smaller must be classic 32-bit BMP entries, that is a BITMAPINFOHEADER with the height doubled (colour bitmap plus mask) followed by pixel data and a 1-bit AND mask. A PNG-compressed entry was accepted only at 256 pixels. The notes do not say whether other sizes above 48 behave either way, nor which Windows or .NET versions were tested beyond .NET 8.
Fix
The icon generator (a PowerShell script writing the .ico by hand) writes BMP entries for the small sizes (16 to 48) and a PNG entry only for 256. The same .ico was also used as the project's application icon. In the tray code the icon came from Icon.ExtractAssociatedIcon on the executable path, with SystemIcons.Application as the fallback.
2. winotify runs the message as part of a PowerShell script
Observation
winotify 1.1.0 shows a toast by writing a temporary Windows PowerShell script and running it with powershell.exe. In the installed module the script template contains a PowerShell here-string with an XML template in which the title and message are placed inside <![CDATA[ ... ]]> sections, and the tag, group and app id are also substituted into the script text. The project that used it noted, in a review on 2026-11-12, that the message text is therefore interpreted by PowerShell and by the XML parser, not just displayed.
Why this matters
This is a correctness problem and a safety problem. Characters that PowerShell treats specially inside a double-quoted here-string (variable and sub-expression syntax, the escape character) change or drop parts of the displayed text. Line breaks and the sequences that end a here-string or a CDATA section can break the generated script or the XML, so the toast silently fails or shows something else. The same mechanism means text that originates outside the program (a meeting subject, a window title, a file name) is not safe to pass in unchanged, because it is evaluated as script rather than shown as data.
Fix used
A small sanitising function runs on every string passed to the toast:
- join all lines into one line;
- break up the CDATA terminator (
]]>becomes]] >); - remove
$and the backtick; - truncate to 300 characters.
The sanitiser is applied to the message. The title in that project was a fixed string, and winotify inserts the title the same way, so any variable title needs the same treatment. The sources do not say whether double quotes need handling; the project removed only the characters above.
Not verified
- Behaviour of other winotify versions. Only 1.1.0 was inspected.
- Whether the sanitiser is complete; it is the project's chosen mitigation, not a proven one. A notification path that does not go through a generated script avoids the issue altogether.
Sources
- Icon item: build notes of a self-contained .NET 8 WinForms tray app, 2026-08, including the icon generator script it uses. Toast item: source and review notes of a Python background service using winotify 1.1.0, 2026-11-12; the generated-script structure was checked in the installed winotify module.