如何允许启动器应用程序固定到任务栏

How to allow launcher application to be pinned to the taskbar

我们有一个简单的 launcher.cmd 脚本,它根据命令行参数设置一些先决条件(网络共享、配置文件等),然后启动第三方应用程序。 它还会启动自定义启动画面(Windows Forms 应用程序),但除启动画面外没有任何功能。外部应用程序还包含一个我们编写的插件。

想象一下这样的事情:

set appEnvironment=%1
MyCustomSplashScreen\MyCustomSplashScreen.exe %appEnvironment%
net use X: /delete /yes
net use X: \someserver\%appEnvironment%
copy Configuration\%appEnvironment%.config ExternalApp\ExternalApp.MyPlugin.config
start ExternalApp\ExternalApp.exe

它是这样调用的:

launcher.cmd Production

我们面临的挑战是一些用户将 ExternalApp.exe(实际应用程序,在 launcher.cmd 和启动画面终止后)固定到任务栏。任务栏快捷方式然后直接启动 ExternalApp.exe 而不是我们的启动器脚本 - 因此可能会发生各种奇怪的事情。 由于我们可以控制插件,因此可以将一些逻辑直接移动到 ExternalApp.exe 中,但这并不能解决丢失环境参数的问题。我能想出的最佳解决方案是确保应用程序只能通过启动器脚本启动的不同方法,从根本上让用户无法将应用程序固定到任务栏。

不过,我想过要更有创意一点。我打算执行以下操作:

  1. 将 launcher.cmd 逻辑移动到 MyCustomSplashScreen.exe
  2. 在 MyCustomSplashScreen.exe 中,启动 ExternalApp.exe 并使其成为停靠子节点 window(参见 Docking Window inside another Window)。
  3. 不使用参数,而是创建反映环境的 MyCustomSplashScreen.exe 的副本(或链接),例如Launch_Production.exe、Launch_Staging.exe等

结果是只有 MyCustomSplashScreen 会出现在任务栏上并且可以固定。固定应用程序会导致特定于该环境(例如 Launch_Staging.exe)的应用程序被固定,这正是用户所期望的。

我非常有信心这会奏效。但也许有更简单的解决方案?我正在寻找的是一些方法,只在任务栏上制作我的启动器应用程序,而不是它启动的应用程序。

我发现了类似的问题 here and ,其中建议操纵固定过程本身。也许这是更好的解决方案?我只是不确定我的插件是否有足够的控制 ExternalApp.exe 来实现这个,所以需要测试它。

的解决方案工作得很好。 我将类似这样的东西放入我的插件中,它完全符合我的要求:

private static void SetTaskbarRelaunchCommand(string environment)
{
    // WARNING, once RelaunchCommand has been set it can't be changed for any given appID.
    // Workaround: delete all links here related to our app.
    // %AppData%\Microsoft\Internet Explorer\Quick Launch\User Pinned\ImplicitAppShortcuts
    // %AppData%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
    // Source: 

    const string appID = "MyAppID";
    string path = $@"C:\Launcher.exe {environment}";
    IntPtr windowHandle = Process.GetCurrentProcess().MainWindowHandle;

    var propGuid = new Guid("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}");
    var id = new PropertyKey(propGuid, 5);                          // System.AppUserModel.ID
    var relaunchCommand = new PropertyKey(propGuid, 2);             // System.AppUserModel.RelaunchCommand
    var relaunchDisplayNameResource = new PropertyKey(propGuid, 4); // System.AppUserModel.RelaunchDisplayNameResource

    WindowProperties.SetWindowProperty(windowHandle, id, appID);
    WindowProperties.SetWindowProperty(windowHandle, relaunchCommand, path);
    WindowProperties.SetWindowProperty(windowHandle, relaunchDisplayNameResource, $"My App {environment}");
}

微软2009年发布的WindowsAPICodePack原版好像已经下线了。相反,有十几个非官方的 NuGet 包(声称)包含原始库,以及一些已修复的错误。 我最终使用了 this NuGet package,因为它似乎得到了积极维护。

当然,我很幸运,我可以通过我的插件来操纵外部应用程序的行为。如果没有这个,除了 运行 将应用程序作为停靠子 window.

之外,可能没有其他解决方案

更新: 当您无法控制正在启动的应用程序时,似乎还有另一种方法可以实现此目的。您只需为启动的应用程序的 window 设置 System.AppUserModel.Id。更多详情 here.

我在最新的 Windows 10 更新中未能成功设置 RelaunchCommand 和 RelaunchDisplayNameResource。但我发现了另一种可能性,可以将启动器固定到任务栏:Pinning to the taskbar a "chained process"