From d18b3f519b528974115fb918c2a339c1ed37b750 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 2 Apr 2026 19:00:48 -0700 Subject: [PATCH 1/4] Bugfix: inconsistent AUMID for unpackaged --- issue-20053-aumid-investigation.md | 120 +++++++++++++++++ .../WindowsTerminal/WindowEmperor.cpp | 127 +++++++++++++++++- src/cascadia/WindowsTerminal/WindowEmperor.h | 3 + 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 issue-20053-aumid-investigation.md diff --git a/issue-20053-aumid-investigation.md b/issue-20053-aumid-investigation.md new file mode 100644 index 00000000000..3ee0767bea0 --- /dev/null +++ b/issue-20053-aumid-investigation.md @@ -0,0 +1,120 @@ +# Issue #20053: Taskbar Shortcut Regression with Unpackaged AUMID + +## Summary + +PR [#20018](https://github.com/microsoft/terminal/pull/20018) introduced +`SetCurrentProcessExplicitAppUserModelID` for unpackaged builds so that future +features (toast notifications, shell integration) have a stable +AppUserModelID (AUMID). This caused a regression: when Windows Terminal is +pinned to the taskbar via a `.lnk` shortcut, launching the exe creates a +**separate** taskbar entry instead of grouping with the pin. + +## Root Cause + +The Windows shell resolves taskbar grouping identity in priority order: + +1. Per-window AUMID (set via `IPropertyStore` on the HWND) +2. Per-process AUMID (`SetCurrentProcessExplicitAppUserModelID`) +3. Auto-derived from the executable path + +Before #20018, neither the pinned `.lnk` nor the process had an explicit +AUMID. Both used the auto-derived identity (from exe path), so they matched +and the taskbar grouped them together. + +After #20018, the process sets an explicit AUMID +(`WindowsTerminalDev..`), but the pinned shortcut still has +no AUMID. The shell sees two different identities — the pin's auto-derived one +and the process's explicit one — and creates separate taskbar entries. + +## Debugging Tools Used + +### Inspecting shortcut AUMIDs + +```powershell +$taskbarPath = "$env:APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar" +$shell = New-Object -ComObject Shell.Application +$folder = $shell.NameSpace($taskbarPath) +foreach ($item in $folder.Items()) { + $aumid = $item.ExtendedProperty("System.AppUserModel.ID") + Write-Host "$($item.Name): AUMID = '$aumid'" +} +``` + +### Inspecting process AUMIDs + +`SetCurrentProcessExplicitAppUserModelID` sets a process-level AUMID that +cannot be read from another process via any public API. +`SHGetPropertyStoreForWindow` only returns per-*window* AUMIDs (which are +separate). We used a memory-scanning PowerShell script +(`scratch/Get-ProcessAumid.ps1`) that scans the target process's committed +memory for UTF-16 strings matching the AUMID pattern. This confirmed which +builds were setting the AUMID. + +### Key observation + +- Shortcut after fresh pin: **no AUMID** (empty) +- Process before #20018: **no AUMID** (auto-derived from exe path) → matches pin +- Process after #20018: **explicit AUMID** (`WindowsTerminalDev..`) → mismatch + +## Attempted Fixes + +### Attempt 1: Stamp the shortcut at launch, skip process AUMID + +**Idea:** When launched from a shortcut (`STARTF_TITLEISLINKNAME`), check if +the `.lnk` has our AUMID. If not, stamp it and skip +`SetCurrentProcessExplicitAppUserModelID` for this launch. + +**Result:** Only handled the "launched from shortcut" case. Double-clicking the +exe directly (not via the pin) still set the process AUMID, causing the same +mismatch. + +### Attempt 2: Enumerate pinned taskbar shortcuts at launch + +**Idea:** At startup, enumerate all `.lnk` files in the pinned taskbar folder +(`%APPDATA%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar`). +Find any shortcut pointing to our exe. If it doesn't have our AUMID, stamp it +and skip the process AUMID. + +**Result:** The stamping itself caused the shell to immediately re-read the +shortcut, updating the pin's cached identity mid-launch. The pin now had the +explicit AUMID while the running window (with no process AUMID set) still used +the auto-derived identity. Still a mismatch, just in the opposite direction. + +### Attempt 3: Defer shortcut stamping to process exit + +**Idea:** Same detection logic, but instead of stamping the shortcut during +startup, defer it to process shutdown (right before `TerminateProcess`). On +the first launch with an unstamped pin, skip both the stamp and the process +AUMID — the window groups correctly because both use the auto-derived identity. +At shutdown, stamp the shortcut. On the next launch, the shortcut has our +AUMID, so we set the process AUMID to match — both agree and grouping works. + +**Result:** Works correctly. + +## Final Fix + +The fix is in `WindowEmperor::_setupAumid()` with three states: + +| State | Pin exists? | Pin has our AUMID? | Action | +|---|---|---|---| +| `NotFound` | No | N/A | Set process AUMID (no conflict possible) | +| `AlreadyStamped` | Yes | Yes | Set process AUMID (they match) | +| `NeedsStamping` | Yes | No | Skip process AUMID; stamp shortcut at shutdown | + +The shortcut stamping at shutdown uses the standard public COM APIs: +`IShellLinkW`, `IPersistFile`, `IPropertyStore`, and `PKEY_AppUserModel_ID`. + +### Trade-off + +On the **very first launch** after pinning (before the shortcut has been +stamped), the process AUMID is not set. This means any features that depend on +the process AUMID (e.g., toast notifications) won't work until the second +launch. This is acceptable because the first launch stamps the shortcut at +exit, so subsequent launches have the full AUMID active. + +## Files Changed + +- `src/cascadia/WindowsTerminal/WindowEmperor.cpp` — Added `_setupAumid()` + method and shutdown stamping logic +- `src/cascadia/WindowsTerminal/WindowEmperor.h` — Added method declaration + and `_pendingAumidLnkPath`/`_pendingAumid` member variables diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 55244118c9d..e1fe0bcb72d 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "AppHost.h" #include "resource.h" @@ -313,6 +315,104 @@ AppHost* WindowEmperor::_mostRecentWindow() const noexcept return mostRecent; } +// Checks if any pinned taskbar shortcut (.lnk) points to our executable. +// If one is found without our AUMID, we skip setting the process AUMID for +// THIS launch (to avoid a mismatch with the shell's cached shortcut identity) +// and instead stamp the shortcut at process exit so the NEXT launch will match. +// If the shortcut already carries our AUMID, we set the process AUMID to match. +// If no matching shortcut is found, we set the process AUMID unconditionally. +void WindowEmperor::_setupAumid(const std::wstring& aumid) +{ + const auto ourExePath = wil::GetModuleFileNameW(nullptr); + + // Search for a pinned taskbar shortcut targeting our executable. + enum class PinState + { + NotFound, + NeedsStamping, + AlreadyStamped, + } pinState = PinState::NotFound; + std::wstring pinnedLnkPath; + + const auto taskbarGlob = wil::ExpandEnvironmentStringsW( + LR"(%APPDATA%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\*.lnk)"); + + WIN32_FIND_DATAW findData{}; + const wil::unique_hfind findHandle{ FindFirstFileW(taskbarGlob.c_str(), &findData) }; + if (findHandle) + { + const auto lastSlash = taskbarGlob.rfind(L'\\'); + const auto taskbarDir = taskbarGlob.substr(0, lastSlash + 1); + + do + { + const auto lnkPath = taskbarDir + findData.cFileName; + + wil::com_ptr shellLink; + if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)))) + { + continue; + } + + const auto persistFile = shellLink.try_query(); + if (!persistFile || FAILED(persistFile->Load(lnkPath.c_str(), STGM_READ))) + { + continue; + } + + wchar_t targetPath[MAX_PATH]{}; + if (FAILED(shellLink->GetPath(targetPath, MAX_PATH, nullptr, SLGP_RAWPATH))) + { + continue; + } + + if (_wcsicmp(targetPath, ourExePath.c_str()) != 0) + { + continue; + } + + // Found a pin pointing to us. Check if it has our AUMID. + pinnedLnkPath = lnkPath; + pinState = PinState::NeedsStamping; + + if (const auto propertyStore = shellLink.try_query()) + { + wil::unique_prop_variant pv; + if (SUCCEEDED(propertyStore->GetValue(PKEY_AppUserModel_ID, &pv)) && + pv.vt == VT_LPWSTR && pv.pwszVal && + aumid == pv.pwszVal) + { + pinState = PinState::AlreadyStamped; + } + } + + break; + } while (FindNextFileW(findHandle.get(), &findData)); + } + + switch (pinState) + { + case PinState::AlreadyStamped: + case PinState::NotFound: + // Either the pin already matches our AUMID, or there's no pin at all. + // Safe to set the process AUMID — no mismatch possible. + LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(aumid.c_str())); + break; + + case PinState::NeedsStamping: + // The pin exists but doesn't have our AUMID yet. The shell has already + // cached the shortcut's identity (auto-derived from the exe path). + // DON'T set the process AUMID or stamp the shortcut now — writing the + // shortcut causes the shell to re-read it immediately, which changes + // the pin's identity mid-launch and creates a mismatch. Instead, + // save the info so we can stamp it at shutdown (right before + // TerminateProcess) when the taskbar association no longer matters. + _pendingAumidLnkPath = pinnedLnkPath; + _pendingAumid = aumid; + break; + } +} + void WindowEmperor::HandleCommandlineArgs(int nCmdShow) { // When running without package identity, set an explicit AppUserModelID so @@ -373,7 +473,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) #else fmt::format_to(std::back_inserter(unpackagedAumid), FMT_COMPILE(L".{:08x}"), hash); #endif - LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(unpackagedAumid.c_str())); + _setupAumid(unpackagedAumid); } } @@ -553,6 +653,31 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) Shell_NotifyIconW(NIM_DELETE, &_notificationIcon); } + // If we deferred stamping a pinned taskbar shortcut with our AUMID + // (to avoid a mid-launch mismatch), do it now before exit. + if (!_pendingAumidLnkPath.empty()) + { + wil::com_ptr shellLink; + if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)))) + { + if (const auto persistFile = shellLink.try_query(); + persistFile && SUCCEEDED(persistFile->Load(_pendingAumidLnkPath.c_str(), STGM_READWRITE))) + { + if (const auto propertyStore = shellLink.try_query()) + { + PROPVARIANT writePv{}; + writePv.vt = VT_LPWSTR; + writePv.pwszVal = const_cast(_pendingAumid.c_str()); + if (SUCCEEDED(propertyStore->SetValue(PKEY_AppUserModel_ID, writePv)) && + SUCCEEDED(propertyStore->Commit())) + { + persistFile->Save(_pendingAumidLnkPath.c_str(), TRUE); + } + } + } + } + } + // There's a mysterious crash in XAML on Windows 10 if you just let _app get destroyed (GH#15410). // We also need to ensure that all UI threads exit before WindowEmperor leaves the scope on the main thread (MSFT:46744208). // Both problems can be solved and the shutdown accelerated by using TerminateProcess. diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 5e2801276c5..bfad14723b3 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -68,6 +68,7 @@ class WindowEmperor void _persistState(const winrt::Microsoft::Terminal::Settings::Model::ApplicationState& state) const; void _finalizeSessionPersistence() const; void _checkWindowsForNotificationIcon(); + void _setupAumid(const std::wstring& aumid); wil::unique_hwnd _window; winrt::TerminalApp::App _app{ nullptr }; @@ -83,6 +84,8 @@ class WindowEmperor std::optional _currentSystemThemeIsDark; int32_t _windowCount = 0; int32_t _messageBoxCount = 0; + std::wstring _pendingAumidLnkPath; + std::wstring _pendingAumid; #if 0 // #ifdef NDEBUG static constexpr void _assertIsMainThread() noexcept From ffaa89eba33f301298b4548337a83ba13174604d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 3 Apr 2026 14:47:54 -0700 Subject: [PATCH 2/4] simplify, add comments, polish --- issue-20053-aumid-investigation.md | 120 ------------------ .../WindowsTerminal/WindowEmperor.cpp | 81 ++++++------ 2 files changed, 41 insertions(+), 160 deletions(-) delete mode 100644 issue-20053-aumid-investigation.md diff --git a/issue-20053-aumid-investigation.md b/issue-20053-aumid-investigation.md deleted file mode 100644 index 3ee0767bea0..00000000000 --- a/issue-20053-aumid-investigation.md +++ /dev/null @@ -1,120 +0,0 @@ -# Issue #20053: Taskbar Shortcut Regression with Unpackaged AUMID - -## Summary - -PR [#20018](https://github.com/microsoft/terminal/pull/20018) introduced -`SetCurrentProcessExplicitAppUserModelID` for unpackaged builds so that future -features (toast notifications, shell integration) have a stable -AppUserModelID (AUMID). This caused a regression: when Windows Terminal is -pinned to the taskbar via a `.lnk` shortcut, launching the exe creates a -**separate** taskbar entry instead of grouping with the pin. - -## Root Cause - -The Windows shell resolves taskbar grouping identity in priority order: - -1. Per-window AUMID (set via `IPropertyStore` on the HWND) -2. Per-process AUMID (`SetCurrentProcessExplicitAppUserModelID`) -3. Auto-derived from the executable path - -Before #20018, neither the pinned `.lnk` nor the process had an explicit -AUMID. Both used the auto-derived identity (from exe path), so they matched -and the taskbar grouped them together. - -After #20018, the process sets an explicit AUMID -(`WindowsTerminalDev..`), but the pinned shortcut still has -no AUMID. The shell sees two different identities — the pin's auto-derived one -and the process's explicit one — and creates separate taskbar entries. - -## Debugging Tools Used - -### Inspecting shortcut AUMIDs - -```powershell -$taskbarPath = "$env:APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar" -$shell = New-Object -ComObject Shell.Application -$folder = $shell.NameSpace($taskbarPath) -foreach ($item in $folder.Items()) { - $aumid = $item.ExtendedProperty("System.AppUserModel.ID") - Write-Host "$($item.Name): AUMID = '$aumid'" -} -``` - -### Inspecting process AUMIDs - -`SetCurrentProcessExplicitAppUserModelID` sets a process-level AUMID that -cannot be read from another process via any public API. -`SHGetPropertyStoreForWindow` only returns per-*window* AUMIDs (which are -separate). We used a memory-scanning PowerShell script -(`scratch/Get-ProcessAumid.ps1`) that scans the target process's committed -memory for UTF-16 strings matching the AUMID pattern. This confirmed which -builds were setting the AUMID. - -### Key observation - -- Shortcut after fresh pin: **no AUMID** (empty) -- Process before #20018: **no AUMID** (auto-derived from exe path) → matches pin -- Process after #20018: **explicit AUMID** (`WindowsTerminalDev..`) → mismatch - -## Attempted Fixes - -### Attempt 1: Stamp the shortcut at launch, skip process AUMID - -**Idea:** When launched from a shortcut (`STARTF_TITLEISLINKNAME`), check if -the `.lnk` has our AUMID. If not, stamp it and skip -`SetCurrentProcessExplicitAppUserModelID` for this launch. - -**Result:** Only handled the "launched from shortcut" case. Double-clicking the -exe directly (not via the pin) still set the process AUMID, causing the same -mismatch. - -### Attempt 2: Enumerate pinned taskbar shortcuts at launch - -**Idea:** At startup, enumerate all `.lnk` files in the pinned taskbar folder -(`%APPDATA%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar`). -Find any shortcut pointing to our exe. If it doesn't have our AUMID, stamp it -and skip the process AUMID. - -**Result:** The stamping itself caused the shell to immediately re-read the -shortcut, updating the pin's cached identity mid-launch. The pin now had the -explicit AUMID while the running window (with no process AUMID set) still used -the auto-derived identity. Still a mismatch, just in the opposite direction. - -### Attempt 3: Defer shortcut stamping to process exit - -**Idea:** Same detection logic, but instead of stamping the shortcut during -startup, defer it to process shutdown (right before `TerminateProcess`). On -the first launch with an unstamped pin, skip both the stamp and the process -AUMID — the window groups correctly because both use the auto-derived identity. -At shutdown, stamp the shortcut. On the next launch, the shortcut has our -AUMID, so we set the process AUMID to match — both agree and grouping works. - -**Result:** Works correctly. - -## Final Fix - -The fix is in `WindowEmperor::_setupAumid()` with three states: - -| State | Pin exists? | Pin has our AUMID? | Action | -|---|---|---|---| -| `NotFound` | No | N/A | Set process AUMID (no conflict possible) | -| `AlreadyStamped` | Yes | Yes | Set process AUMID (they match) | -| `NeedsStamping` | Yes | No | Skip process AUMID; stamp shortcut at shutdown | - -The shortcut stamping at shutdown uses the standard public COM APIs: -`IShellLinkW`, `IPersistFile`, `IPropertyStore`, and `PKEY_AppUserModel_ID`. - -### Trade-off - -On the **very first launch** after pinning (before the shortcut has been -stamped), the process AUMID is not set. This means any features that depend on -the process AUMID (e.g., toast notifications) won't work until the second -launch. This is acceptable because the first launch stamps the shortcut at -exit, so subsequent launches have the full AUMID active. - -## Files Changed - -- `src/cascadia/WindowsTerminal/WindowEmperor.cpp` — Added `_setupAumid()` - method and shutdown stamping logic -- `src/cascadia/WindowsTerminal/WindowEmperor.h` — Added method declaration - and `_pendingAumidLnkPath`/`_pendingAumid` member variables diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index e1fe0bcb72d..28bb09f0f30 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -315,23 +315,32 @@ AppHost* WindowEmperor::_mostRecentWindow() const noexcept return mostRecent; } -// Checks if any pinned taskbar shortcut (.lnk) points to our executable. -// If one is found without our AUMID, we skip setting the process AUMID for -// THIS launch (to avoid a mismatch with the shell's cached shortcut identity) -// and instead stamp the shortcut at process exit so the NEXT launch will match. -// If the shortcut already carries our AUMID, we set the process AUMID to match. -// If no matching shortcut is found, we set the process AUMID unconditionally. +// GH#20053: The shell resolves taskbar grouping identity as: per-window AUMID > +// per-process AUMID > auto-derived from exe path. Before we started setting a +// process AUMID, both the pinned .lnk and the process used auto-derived +// identity, so they matched. Now that we set an explicit AUMID, a pinned .lnk +// that predates the AUMID change has no AUMID and still uses auto-derived +// identity, causing a mismatch and a duplicate taskbar button. +// +// To fix this, we check if a pinned taskbar shortcut (.lnk) points to our exe. +// If it already carries our AUMID (or no pin exists), we set the process AUMID +// normally. If a pin exists WITHOUT our AUMID, we skip setting the process +// AUMID for THIS launch (both sides use auto-derived identity, so they match) +// and defer stamping the shortcut to process exit. On the next launch, the pin +// has our AUMID, so we set the process AUMID to match, and both agree. +// +// NOTE: On the first launch after pinning, the process AUMID is not set. If +// toast notifications are needed in the future, use +// ToastNotificationManager::CreateToastNotifier(aumid) with the AUMID string +// directly. That API does not depend on SetCurrentProcessExplicitAppUserModelID. +// A Start Menu shortcut with the AUMID (separate from the taskbar pin) is also +// required for toast routing; see +// https://learn.microsoft.com/windows/apps/develop/notifications/app-notifications/send-local-toast-other-apps void WindowEmperor::_setupAumid(const std::wstring& aumid) { const auto ourExePath = wil::GetModuleFileNameW(nullptr); - // Search for a pinned taskbar shortcut targeting our executable. - enum class PinState - { - NotFound, - NeedsStamping, - AlreadyStamped, - } pinState = PinState::NotFound; + bool needsDeferredStamping = false; std::wstring pinnedLnkPath; const auto taskbarGlob = wil::ExpandEnvironmentStringsW( @@ -371,9 +380,10 @@ void WindowEmperor::_setupAumid(const std::wstring& aumid) continue; } - // Found a pin pointing to us. Check if it has our AUMID. + // Found a pin pointing to us. Assume it needs stamping unless + // we confirm it already has our AUMID. pinnedLnkPath = lnkPath; - pinState = PinState::NeedsStamping; + needsDeferredStamping = true; if (const auto propertyStore = shellLink.try_query()) { @@ -382,7 +392,7 @@ void WindowEmperor::_setupAumid(const std::wstring& aumid) pv.vt == VT_LPWSTR && pv.pwszVal && aumid == pv.pwszVal) { - pinState = PinState::AlreadyStamped; + needsDeferredStamping = false; } } @@ -390,26 +400,19 @@ void WindowEmperor::_setupAumid(const std::wstring& aumid) } while (FindNextFileW(findHandle.get(), &findData)); } - switch (pinState) + if (needsDeferredStamping) { - case PinState::AlreadyStamped: - case PinState::NotFound: - // Either the pin already matches our AUMID, or there's no pin at all. - // Safe to set the process AUMID — no mismatch possible. - LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(aumid.c_str())); - break; - - case PinState::NeedsStamping: - // The pin exists but doesn't have our AUMID yet. The shell has already - // cached the shortcut's identity (auto-derived from the exe path). - // DON'T set the process AUMID or stamp the shortcut now — writing the - // shortcut causes the shell to re-read it immediately, which changes - // the pin's identity mid-launch and creates a mismatch. Instead, - // save the info so we can stamp it at shutdown (right before - // TerminateProcess) when the taskbar association no longer matters. - _pendingAumidLnkPath = pinnedLnkPath; + // The pin exists but doesn't have our AUMID yet. Don't set the process + // AUMID or stamp the shortcut now. Writing the shortcut causes the + // shell to re-read it immediately, changing the pin's cached identity + // mid-launch and creating a mismatch in the opposite direction. Instead, + // stamp it at shutdown when the taskbar association no longer matters. + _pendingAumidLnkPath = std::move(pinnedLnkPath); _pendingAumid = aumid; - break; + } + else + { + LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(aumid.c_str())); } } @@ -653,8 +656,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) Shell_NotifyIconW(NIM_DELETE, &_notificationIcon); } - // If we deferred stamping a pinned taskbar shortcut with our AUMID - // (to avoid a mid-launch mismatch), do it now before exit. + // GH#20053: Deferred shortcut stamping. See _setupAumid() for context. if (!_pendingAumidLnkPath.empty()) { wil::com_ptr shellLink; @@ -665,10 +667,9 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) { if (const auto propertyStore = shellLink.try_query()) { - PROPVARIANT writePv{}; - writePv.vt = VT_LPWSTR; - writePv.pwszVal = const_cast(_pendingAumid.c_str()); - if (SUCCEEDED(propertyStore->SetValue(PKEY_AppUserModel_ID, writePv)) && + wil::unique_prop_variant pv; + if (SUCCEEDED(InitPropVariantFromString(_pendingAumid.c_str(), &pv)) && + SUCCEEDED(propertyStore->SetValue(PKEY_AppUserModel_ID, pv)) && SUCCEEDED(propertyStore->Commit())) { persistFile->Save(_pendingAumidLnkPath.c_str(), TRUE); From 73af681149d8059d3116d390b7eacdfa60e40573 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 3 Apr 2026 15:13:14 -0700 Subject: [PATCH 3/4] wcsicmp is a word --- .github/actions/spelling/expect/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 6c226753b98..082faa747dc 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1882,6 +1882,7 @@ WCIA WCIW wcs WCSHELPER +wcsicmp wcsrev wcswidth wddm From dad44d58b66d963a9843a64f86724d4e5082a703 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 29 Apr 2026 19:57:19 -0700 Subject: [PATCH 4/4] address feedback --- .github/actions/spelling/expect/expect.txt | 1 - src/cascadia/WindowsTerminal/WindowEmperor.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 082faa747dc..6c226753b98 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1882,7 +1882,6 @@ WCIA WCIW wcs WCSHELPER -wcsicmp wcsrev wcswidth wddm diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 28bb09f0f30..da1fbb64fdd 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -347,7 +347,7 @@ void WindowEmperor::_setupAumid(const std::wstring& aumid) LR"(%APPDATA%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\*.lnk)"); WIN32_FIND_DATAW findData{}; - const wil::unique_hfind findHandle{ FindFirstFileW(taskbarGlob.c_str(), &findData) }; + const wil::unique_hfind findHandle{ FindFirstFileExW(taskbarGlob.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH) }; if (findHandle) { const auto lastSlash = taskbarGlob.rfind(L'\\'); @@ -375,7 +375,7 @@ void WindowEmperor::_setupAumid(const std::wstring& aumid) continue; } - if (_wcsicmp(targetPath, ourExePath.c_str()) != 0) + if (til::compare_ordinal_insensitive(targetPath, ourExePath) != 0) { continue; }