Reputation: 704
When you create a windows form in PowerShell, it will group with the host console window, even if you change the icon for the form.
How do I separate the new form's taskbar icon out from the PowerShell console icon?
What happens on left, desired effect on right:
Example code:
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(300,200)
$form.ShowInTaskbar = $true
$form.Icon = New-Object system.drawing.icon 'c:\icon.ico'
$form.Text = 'New taskbar icon plz'
$form.BringToFront()
$form.ShowDialog()
The only thing I saw that was somewhat helpful was a reference that changing the "Application ID" would separate this out, but the references are all C code.
https://msdn.microsoft.com/en-us/magazine/dd942846.aspx
Please give PowerShell answers, or if the answers include C# code or calls to other APIs, please explain how they work in PowerShell.
Upvotes: 5
Views: 1909
Reputation: 29033
After cobbling together a lot of P/Invoke through C# magic from the internet, mostly from here and some from here, I ended up with this to add a type with one method you can use like [PSAppID]::SetAppIdForWindow($form.handle, "Your.AppId")
:
$signature = @'
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
public class PSAppID
{
// https://emoacht.wordpress.com/2012/11/14/csharp-appusermodelid/
// IPropertyStore Interface
[ComImport,
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
private interface IPropertyStore
{
uint GetCount([Out] out uint cProps);
uint GetAt([In] uint iProp, out PropertyKey pkey);
uint GetValue([In] ref PropertyKey key, [Out] PropVariant pv);
uint SetValue([In] ref PropertyKey key, [In] PropVariant pv);
uint Commit();
}
// PropertyKey Structure
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct PropertyKey
{
private Guid formatId; // Unique GUID for property
private Int32 propertyId; // Property identifier (PID)
public Guid FormatId
{
get
{
return formatId;
}
}
public Int32 PropertyId
{
get
{
return propertyId;
}
}
public PropertyKey(Guid formatId, Int32 propertyId)
{
this.formatId = formatId;
this.propertyId = propertyId;
}
public PropertyKey(string formatId, Int32 propertyId)
{
this.formatId = new Guid(formatId);
this.propertyId = propertyId;
}
}
// PropVariant Class (only for string value)
[StructLayout(LayoutKind.Explicit)]
public class PropVariant : IDisposable
{
[FieldOffset(0)]
ushort valueType; // Value type
// [FieldOffset(2)]
// ushort wReserved1; // Reserved field
// [FieldOffset(4)]
// ushort wReserved2; // Reserved field
// [FieldOffset(6)]
// ushort wReserved3; // Reserved field
[FieldOffset(8)]
IntPtr ptr; // Value
// Value type (System.Runtime.InteropServices.VarEnum)
public VarEnum VarType
{
get { return (VarEnum)valueType; }
set { valueType = (ushort)value; }
}
public bool IsNullOrEmpty
{
get
{
return (valueType == (ushort)VarEnum.VT_EMPTY ||
valueType == (ushort)VarEnum.VT_NULL);
}
}
// Value (only for string value)
public string Value
{
get
{
return Marshal.PtrToStringUni(ptr);
}
}
public PropVariant()
{ }
public PropVariant(string value)
{
if (value == null)
throw new ArgumentException("Failed to set value.");
valueType = (ushort)VarEnum.VT_LPWSTR;
ptr = Marshal.StringToCoTaskMemUni(value);
}
~PropVariant()
{
Dispose();
}
public void Dispose()
{
PropVariantClear(this);
GC.SuppressFinalize(this);
}
}
[DllImport("Ole32.dll", PreserveSig = false)]
private extern static void PropVariantClear([In, Out] PropVariant pvar);
[DllImport("shell32.dll")]
private static extern int SHGetPropertyStoreForWindow(
IntPtr hwnd,
ref Guid iid /*IID_IPropertyStore*/,
[Out(), MarshalAs(UnmanagedType.Interface)] out IPropertyStore propertyStore);
public static void SetAppIdForWindow(int handle, string AppId)
{
Guid iid = new Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99");
IPropertyStore prop;
int result1 = SHGetPropertyStoreForWindow((IntPtr)handle, ref iid, out prop);
// Name = System.AppUserModel.ID
// ShellPKey = PKEY_AppUserModel_ID
// FormatID = 9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3
// PropID = 5
// Type = String (VT_LPWSTR)
PropertyKey AppUserModelIDKey = new PropertyKey("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}", 5);
PropVariant pv = new PropVariant(AppId);
uint result2 = prop.SetValue(ref AppUserModelIDKey, pv);
Marshal.ReleaseComObject(prop);
}
}
'@
Add-Type -TypeDefinition $signature
And then with your form:
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(300,200)
$form.ShowInTaskbar = $true
$form.visible = $true
[PSAppID]::SetAppIdForWindow($form.Handle, "YourName.App")
It splits off into its own taskbar entry.
CompanyName.ProductName.SubProduct.VersionInformation
where the SubProduct is optional, and the VersionInformation is present only if you want different versions of your app to be treated as distinct." and no longer than 128 characters.
Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")
is supposed to be a Windows Shell ID, of IPropertyStore, but I don't know how to get it without just writing it in.AppUserModelIDKey
magic GUID which comes from some Win32 propkey.h
maybe?See also:
Upvotes: 6