Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pinning to the taskbar a "chained process"

Let's say I have two programs called launcher.exe and launchee.exe. The launcher display a button which -when clicked- starts launchee.exe and launchee.exe is a simple hello world program.

If I do nothing to prevent it, when the user will "pin to the taskbar" the hello world program, it will pin launchee.exe and will not go through the launcher to start the application.

What is the best way to tell Windows to pin launcher.exe and not launchee.exe ?

To make things concrete here's an example of implementation of launcher.exe in C#:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;

public class Launcher : Form
{
    static public void Main ()
    {
        Application.Run (new Launcher ());
    }

    public Launcher ()
    {
        Button b = new Button ();
        b.Text = "Launch";
        b.Click += new EventHandler (Button_Click);
        Controls.Add (b);
    }

    private void Button_Click (object sender, EventArgs e)
    {
        Process.Start("launchee.exe");
        System.Environment.Exit(0);
    }
}

and launchee.exe:

using System;
using System.Drawing;
using System.Windows.Forms;

public class Launchee : Form
{
    static public void Main ()
    {
        Application.Run (new Launchee ());
    }

    public Launchee ()
    {
        Label b = new Label();
        b.Text = "Hello World !";
        Controls.Add (b);
    }
}
like image 449
Marc Avatar asked Oct 25 '25 23:10

Marc


1 Answers

I propose an anwser based on binding the low level API to access the AppUserModelID. I find this solution fragile and messy. It is largely inspired by the Windows Api CodePack that seems to have been discontinued by Microsoft. I hope someone will propose a cleaner solution.

Its purpose is to set the AppUserId to be "Stackoverflow.chain.process.pinning" and manually set the RelaunchCommand as well as the DisplayName properties (they have to be set together according to AppUserModelID).

To use it in the example implementation, one needs to call TaskBar.SetupLauncher(this) and TaskBar.SetupLaunchee(this) respectively in the Launcher and Launchee constructors.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

internal struct PropertyKey
{
  Guid formatId;
  int propertyId;

  internal PropertyKey(Guid guid, int propertyId)
  {
      this.formatId = guid;
      this.propertyId = propertyId;
  }
}

[StructLayout(LayoutKind.Explicit)]
internal struct PropVariant
{
  [FieldOffset(0)] internal ushort vt;
  [FieldOffset(8)] internal IntPtr pv;
  [FieldOffset(8)] internal UInt64 padding;

  [DllImport("Ole32.dll", PreserveSig = false)]
  internal static extern void PropVariantClear(ref PropVariant pvar);

  internal PropVariant(string value)
  {
      this.vt = (ushort)VarEnum.VT_LPWSTR;
      this.padding = 0;
      this.pv = Marshal.StringToCoTaskMemUni(value);
  }

  internal void Clear()
  {
    PropVariantClear (ref this);
  }
}

[ComImport,
Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore
{
  int GetCount([Out] out uint propertyCount);
  void GetAt([In] uint propertyIndex, [Out, MarshalAs(UnmanagedType.Struct)] out PropertyKey key);
  void GetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey key, [Out, MarshalAs(UnmanagedType.Struct)] out PropVariant pv);
  void SetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey key, [In, MarshalAs(UnmanagedType.Struct)] ref PropVariant pv);
  void Commit();
}

internal static class TaskBar {
  [DllImport("shell32.dll")]
  static extern int SHGetPropertyStoreForWindow(
      IntPtr hwnd,
      ref Guid iid /*IID_IPropertyStore*/,
      [Out(), MarshalAs(UnmanagedType.Interface)]out IPropertyStore propertyStore);

  internal static class Key {
    private static Guid propGuid = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3");
    internal static PropertyKey AppId = new PropertyKey(propGuid, 5);
    internal static PropertyKey RelaunchCommand = new PropertyKey(propGuid, 2);
    internal static PropertyKey DisplayName = new PropertyKey(propGuid, 4);
  }

  private static void ClearValue(IPropertyStore store, PropertyKey key) {
      var prop = new PropVariant();
      prop.vt = (ushort)VarEnum.VT_EMPTY;
      store.SetValue(ref key, ref prop);
  }

  private static void SetValue(IPropertyStore store, PropertyKey key, string value) {
    var prop = new PropVariant(value);
    store.SetValue(ref key, ref prop);
    prop.Clear();
  }

  internal static IPropertyStore Store(IntPtr handle) {
    IPropertyStore store;
    var store_guid = new Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99");
    int rc = SHGetPropertyStoreForWindow(handle, ref store_guid, out store);
    if (rc != 0) throw Marshal.GetExceptionForHR(rc);
    return store;
  }

  internal static void SetupLauncher(Form form) {
    IntPtr handle = form.Handle;
    var store = Store(handle);
    SetValue (store, Key.AppId, "Stackoverflow.chain.process.pinning");
    form.FormClosed += delegate { Cleanup(handle); };
  } 

  internal static void SetupLaunchee(Form form) {
    IntPtr handle = form.Handle;
    var store = Store(handle);
    SetValue (store, Key.AppId, "Stackoverflow.chain.process.pinning");
    string exePath = System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "launcher.exe");
    SetValue (store, Key.RelaunchCommand, exePath);
    SetValue (store, Key.DisplayName, "Test");
    form.FormClosed += delegate { Cleanup(handle); };
  }

  internal static void Cleanup(IntPtr handle) {
    var store = Store(handle);
    ClearValue (store, Key.AppId);
    ClearValue (store, Key.RelaunchCommand);
    ClearValue (store, Key.DisplayName);
  }
}
like image 94
Marc Avatar answered Oct 27 '25 12:10

Marc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!