Repository: microsoft/PowerToys
Pull request #38027: Do not show CmdPal on startup
Author: login: stefansjfw
Labels: name: Priority-0
description: Bugs that we consider release-blocking/recall-class (P0)
name: Product-Command Palette
description: Refers to the Command Palette utility
Created: 2025-03-19T10:16:42Z | Merged: 2025-03-20T14:48:15Z

Related to https://github.com/microsoft/PowerToys/pull/37908


Diff:
diff --git a/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp b/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
index 419e66c64864..86244194eced 100644
--- a/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
+++ b/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
@@ -12,6 +12,7 @@
 #include <common/utils/process_path.h>
 #include <Psapi.h>
 #include <TlHelp32.h>
+#include <common/utils/winapi_error.h>
 
 HINSTANCE g_hInst_cmdPal = 0;
 
@@ -42,28 +43,24 @@ class CmdPal : public PowertoyModuleIface
     //contains the non localized key of the powertoy
     std::wstring app_key;
 
-    void LaunchApp()
+    void LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
     {
-        auto package = package::GetRegisteredPackage(L"Microsoft.CommandPalette", false);
-
-        if (package.has_value())
+        std::wstring dir = std::filesystem::path(appPath).parent_path();
+
+        SHELLEXECUTEINFO sei = { 0 };
+        sei.cbSize = sizeof(SHELLEXECUTEINFO);
+        sei.hwnd = nullptr;
+        sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
+        sei.lpVerb = elevated ? L"runas" : L"open";
+        sei.lpFile = appPath.c_str();
+        sei.lpParameters = commandLineArgs.c_str();
+        sei.lpDirectory = dir.c_str();
+        sei.nShow = SW_SHOWNORMAL;
+
+        if (!ShellExecuteEx(&sei))
         {
-            auto getAppListEntriesOperation = package->GetAppListEntriesAsync();
-            auto appEntries = getAppListEntriesOperation.get();
-
-            if (appEntries.Size() > 0)
-            {
-                winrt::Windows::Foundation::IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
-                launchOperation.get();
-            }
-            else
-            {
-                Logger::error(L"No app entries found for the package.");
-            }
-        }
-        else
-        {
-            Logger::error(L"CmdPal package is not registered.");
+            std::wstring error = get_last_error_or_default(GetLastError());
+            Logger::error(L"Failed to launch process. {}", error);
         }
     }
 
@@ -235,7 +232,11 @@ class CmdPal : public PowertoyModuleIface
             Logger::error(errorMessage);
         }
 
-        LaunchApp();
+#if _DEBUG
+        LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette.Dev_8wekyb3d8bbwe!App", L"RunFromPT", false);
+#else
+        LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette_8wekyb3d8bbwe!App", L"RunFromPT", false);
+#endif
     }
 
     virtual void disable()
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs
index 530f3d8b0bdc..bb6c0a1ca9b9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs
@@ -64,7 +64,23 @@ public App()
     protected override void OnLaunched(LaunchActivatedEventArgs args)
     {
         AppWindow = new MainWindow();
-        AppWindow.Activate();
+
+        var cmdArgs = Environment.GetCommandLineArgs();
+
+        bool runFromPT = false;
+        foreach (var arg in cmdArgs)
+        {
+            if (arg == "RunFromPT")
+            {
+                runFromPT = true;
+                break;
+            }
+        }
+
+        if (!runFromPT)
+        {
+            AppWindow.Activate();
+        }
     }
 
     /// <summary>
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
index c09d1c9688a6..4c5bd12ccce7 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
@@ -466,6 +466,8 @@ private LRESULT HotKeyPrc(
                         // so that we can bind hotkeys to individual commands
                         if (!this.Visible || !isRootHotkey)
                         {
+                            Activate();
+
                             Summon(hotkey.CommandId);
                         }
                         else if (isRootHotkey)


Reviews:
user: login: zadjii-msft
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T19:15:38Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR updates the CmdPal startup behavior to prevent the window from showing automatically unless launched by PowerToys.  
- Introduces a command line argument check in App.xaml.cs for the "RunFromPT" flag to conditionally activate the window.  
- Adds an Activate() call in MainWindow.xaml.cs during hotkey processing to ensure the window is brought forward when handling hotkey commands.

### Reviewed Changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.

| File | Description |
| ---- | ----------- |
| src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs | Added command line argument logic to conditionally skip window activation on startup. |
| src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs | Added an Activate() call before summoning commands via hotkeys. |

<details>
<summary>Files not reviewed (1)</summary>

* **src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (3)</summary>

**src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs:70**
* [nitpick] Consider renaming 'runFromPT' to a more descriptive name such as 'isLaunchedFromPowerToys' to improve clarity.
```
bool runFromPT = false;
```
**src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs:73**
* Using the magic string "RunFromPT" directly may lead to inconsistencies; consider defining it as a constant.
```
if (arg == "RunFromPT")
```
**src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs:469**
* Verify that invoking Activate() in this context does not conflict with the intended hidden startup behavior, particularly for non-root hotkeys.
```
Activate();
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T04:52:59Z
user: login: shuaiyuanxx
body: No issues were found during the test.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T07:01:14Z
user: login: moooyo
body: I prefer merge in first. We can use a better implementation in the future.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T07:58:16Z
user: login: stefansjfw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T08:07:04Z
user: login: stefansjfw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T13:44:45Z
user: login: zadjii-msft
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T14:46:44Z
user: login: zadjii-msft
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T14:46:56Z

Review comments:
diff_hunk: @@ -42,28 +43,24 @@ class CmdPal : public PowertoyModuleIface
     //contains the non localized key of the powertoy
     std::wstring app_key;
 
-    void LaunchApp()
+    void LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
     {
-        auto package = package::GetRegisteredPackage(L"Microsoft.CommandPalette", false);
-
-        if (package.has_value())
+        std::wstring dir = std::filesystem::path(appPath).parent_path();
+
+        SHELLEXECUTEINFO sei = { 0 };
+        sei.cbSize = sizeof(SHELLEXECUTEINFO);
+        sei.hwnd = nullptr;
+        sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
+        sei.lpVerb = elevated ? L"runas" : L"open";
+        sei.lpFile = appPath.c_str();
+        sei.lpParameters = commandLineArgs.c_str();
+        sei.lpDirectory = dir.c_str();
+        sei.nShow = SW_SHOWNORMAL;
path: src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
user: login: zadjii-msft
body: if we instead stick `SW_HIDE` in here, and get rid of the arg, and instead get the `PInvoke.GetStartupInfo()`, can we see if SW_HIDE was set there, and not show the window in that case?

(I feel like I don't really want to add a magic hidden arg to our exe, but I'll accept it for now if it's the only way)
created_at: 2025-03-19T19:15:38Z
author_association: MEMBER
diff_hunk: @@ -42,28 +43,24 @@ class CmdPal : public PowertoyModuleIface
     //contains the non localized key of the powertoy
     std::wstring app_key;
 
-    void LaunchApp()
+    void LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
     {
-        auto package = package::GetRegisteredPackage(L"Microsoft.CommandPalette", false);
-
-        if (package.has_value())
+        std::wstring dir = std::filesystem::path(appPath).parent_path();
+
+        SHELLEXECUTEINFO sei = { 0 };
+        sei.cbSize = sizeof(SHELLEXECUTEINFO);
+        sei.hwnd = nullptr;
+        sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
+        sei.lpVerb = elevated ? L"runas" : L"open";
+        sei.lpFile = appPath.c_str();
+        sei.lpParameters = commandLineArgs.c_str();
+        sei.lpDirectory = dir.c_str();
+        sei.nShow = SW_SHOWNORMAL;
path: src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
user: login: stefansjfw
body: I'll give it a try
created_at: 2025-03-20T08:07:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,28 +43,24 @@ class CmdPal : public PowertoyModuleIface
     //contains the non localized key of the powertoy
     std::wstring app_key;
 
-    void LaunchApp()
+    void LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
     {
-        auto package = package::GetRegisteredPackage(L"Microsoft.CommandPalette", false);
-
-        if (package.has_value())
+        std::wstring dir = std::filesystem::path(appPath).parent_path();
+
+        SHELLEXECUTEINFO sei = { 0 };
+        sei.cbSize = sizeof(SHELLEXECUTEINFO);
+        sei.hwnd = nullptr;
+        sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
+        sei.lpVerb = elevated ? L"runas" : L"open";
+        sei.lpFile = appPath.c_str();
+        sei.lpParameters = commandLineArgs.c_str();
+        sei.lpDirectory = dir.c_str();
+        sei.nShow = SW_SHOWNORMAL;
path: src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
user: login: stefansjfw
body: doesn't work. even if I pass SW_HIDE when launching, PInvoke says SW_SHOW is there. No idea why
created_at: 2025-03-20T13:44:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,28 +43,24 @@ class CmdPal : public PowertoyModuleIface
     //contains the non localized key of the powertoy
     std::wstring app_key;
 
-    void LaunchApp()
+    void LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
     {
-        auto package = package::GetRegisteredPackage(L"Microsoft.CommandPalette", false);
-
-        if (package.has_value())
+        std::wstring dir = std::filesystem::path(appPath).parent_path();
+
+        SHELLEXECUTEINFO sei = { 0 };
+        sei.cbSize = sizeof(SHELLEXECUTEINFO);
+        sei.hwnd = nullptr;
+        sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
+        sei.lpVerb = elevated ? L"runas" : L"open";
+        sei.lpFile = appPath.c_str();
+        sei.lpParameters = commandLineArgs.c_str();
+        sei.lpDirectory = dir.c_str();
+        sei.nShow = SW_SHOWNORMAL;
path: src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
user: login: zadjii-msft
body: damn. Mmk. We can come up with something more clever in the future. Thanks for taking a look!
created_at: 2025-03-20T14:46:44Z
author_association: MEMBER

PR comments:
user: login: zadjii-msft
created_at: 2025-03-19T11:15:26Z
body: Does this result in a cold launch from the Start Menu / taskbar not showing the window? Is there any way for us to differentiate the taskbar launch case from the PT enable case?
author_association: MEMBER
user: login: stefansjfw
created_at: 2025-03-19T11:43:41Z
body: > Does this result in a cold launch from the Start Menu / taskbar not showing the window? Is there any way for us to differentiate the taskbar launch case from the PT enable case?

I guess we can pass some dummy arg to CmdPal when starting from PowerToys... 
author_association: CONTRIBUTOR
user: login: zadjii-msft
created_at: 2025-03-19T13:24:20Z
body: wow `windows.applicationmodel.core.applistentry` really doesn't give us a lot to work with. 

I suppose we could add an app execution alias, and then `CreateProcess(...., SW_HIDE)` to create the window hidden when launched from PT. App execution aliases are notoriously flaky though
author_association: MEMBER
user: login: stefansjfw
created_at: 2025-03-19T16:32:21Z
body: I tried SW_HIDE, doesn't work :(  It's either shown for a second and then hidden or not hidden at all. I guess Window.Activate() overrides it somehow
author_association: CONTRIBUTOR
user: login: stefansjfw
created_at: 2025-03-19T16:39:23Z
body: never mind, I did what you need. Take a look
author_association: CONTRIBUTOR
user: login: zadjii-msft
created_at: 2025-04-07T21:52:32Z
body: I'm putting notes in here, because #38393 is off the rails

--------------------


I bet we can use this somehow:

https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/applifecycle/applifecycle-rich-activation#activation-details-for-packaged-apps

rather than trying to use the `shell:AppsFolder` thing, which clearly doesn't work immediately on install always
author_association: MEMBER