This test searches for example the verb "properties" and executes it (for a file E:\test.jpg)
(you can also just use ShellExecute(Ex) for standard verbs or Process.Start)
You can uncomment the "TrackPopupMenu..." part to display the context menu (WM_INITMENUPOPUP is needed for submenus)
IContextMenu2 pContextMenu2 = null;
public const int WM_INITMENUPOPUP = 0x0117;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_INITMENUPOPUP)
{
if (pContextMenu2 != null)
pContextMenu2.HandleMenuMsg((uint)m.Msg, (int)m.WParam, m.LParam);
return;
}
else
base.WndProc(ref m);
}
IntPtr pItemIDL = ILCreateFromPath("E:\\test.jpg");
IntPtr pcm = IntPtr.Zero;
Guid IID_IContextMenu = new Guid("000214E4-0000-0000-C000-000000000046");
HRESULT hr = SHGetUIObjectFromFullPIDL(pItemIDL, IntPtr.Zero, ref IID_IContextMenu, ref pcm);
if (hr == HRESULT.S_OK)
{
IContextMenu pContextMenu = Marshal.GetObjectForIUnknown(pcm) as IContextMenu;
IntPtr hMenu = CreatePopupMenu();
//IntPtr hSubmenu = IntPtr.Zero;
IntPtr hSubmenu = hMenu;
//hr = pContextMenu.QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_EXPLORE |CMF_ASYNCVERBSTATE | CMF_INCLUDESTATIC);
hr = pContextMenu.QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_EXPLORE);
if (hr == HRESULT.S_OK)
{
pContextMenu2 = (IContextMenu2)pContextMenu;
hSubmenu = hMenu;
//int nX = Cursor.Position.X, nY = Cursor.Position.Y;
//uint nCmd = TrackPopupMenu(hSubmenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, nX, nY, 0, this.Handle, IntPtr.Zero);
//if (nCmd != 0)
//{
// CMINVOKECOMMANDINFO cmi = new CMINVOKECOMMANDINFO();
// cmi.cbSize = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO));
// cmi.fMask = 0;
// cmi.hwnd = this.Handle;
// cmi.lpVerb = (IntPtr)(nCmd - 1);
// cmi.lpParameters = IntPtr.Zero;
// cmi.lpDirectory = IntPtr.Zero;
// cmi.nShow = SW_SHOWNORMAL;
// cmi.dwHotKey = 0;
// cmi.hIcon = IntPtr.Zero; ;
// hr = pContextMenu.InvokeCommand(ref cmi);
//}
int nNbItems = GetMenuItemCount(hSubmenu);
for (int i = nNbItems - 1; i >= 0; i--)
{
MENUITEMINFO mii = new MENUITEMINFO();
mii.cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO));
mii.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_SUBMENU | MIIM_DATA;
if (GetMenuItemInfo(hMenu, i, true, ref mii))
{
if (mii.fType == MFT_STRING)
{
StringBuilder sbVerb = new StringBuilder(260);
hr = pContextMenu2.GetCommandString(mii.wID - 1, GCS_VERBW, IntPtr.Zero, sbVerb, (uint)sbVerb.Capacity);
if (hr == HRESULT.S_OK)
{
if (sbVerb.ToString() == "properties")
{
CMINVOKECOMMANDINFO cmi = new CMINVOKECOMMANDINFO();
cmi.cbSize = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO));
cmi.fMask = 0;
cmi.hwnd = this.Handle;
cmi.lpVerb = (IntPtr)(mii.wID - 1);
cmi.lpParameters = IntPtr.Zero;
cmi.lpDirectory = IntPtr.Zero;
cmi.nShow = SW_SHOWNORMAL;
cmi.dwHotKey = 0;
cmi.hIcon = IntPtr.Zero; ;
hr = pContextMenu.InvokeCommand(ref cmi);
}
}
}
}
}
pContextMenu2 = null;
}
Marshal.ReleaseComObject(pContextMenu);
DestroyMenu(hMenu);
}
ILFree(pItemIDL);
return;
}
public enum HRESULT : int
{
S_OK = 0,
S_FALSE = 1,
E_NOINTERFACE = unchecked((int)0x80004002),
E_NOTIMPL = unchecked((int)0x80004001),
E_FAIL = unchecked((int)0x80004005),
E_UNEXPECTED = unchecked((int)0x8000FFFF),
E_OUTOFMEMORY = unchecked((int)0x8007000E)
}
[DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr ILCreateFromPath([MarshalAs(UnmanagedType.LPWStr)]string pszPath);
// From MSDN : https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shbrowseforfolderw
private HRESULT SHGetUIObjectFromFullPIDL(IntPtr pidl, IntPtr hwnd, ref Guid riid, ref IntPtr ppv)
{
IntPtr pidlChild = IntPtr.Zero;
IShellFolder psf = null;
ppv = IntPtr.Zero;
Guid IID_IShellFolder = new Guid("000214E6-0000-0000-C000-000000000046");
HRESULT hr = SHBindToParent(pidl, ref IID_IShellFolder, ref psf, ref pidlChild);
if (hr == HRESULT.S_OK)
{
uint rgfReserved = 0;
hr = psf.GetUIObjectOf(hwnd, 1, ref pidlChild, riid, ref rgfReserved, out ppv);
Marshal.ReleaseComObject(psf);
}
return hr;
}
[DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern void ILFree(IntPtr pidl);
[DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern HRESULT SHBindToParent(IntPtr pidl, ref Guid riid, ref IShellFolder ppv, ref IntPtr ppidlLast);
[ComImport]
[Guid("000214e4-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IContextMenu
{
HRESULT QueryContextMenu(IntPtr hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
[PreserveSig()]
HRESULT InvokeCommand(ref CMINVOKECOMMANDINFO pici);
[PreserveSig()]
HRESULT GetCommandString(uint idCmd, uint uType, IntPtr pReserved, StringBuilder pszName, uint cchMax);
}
[ComImport]
[Guid("000214f4-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IContextMenu2 : IContextMenu
{
new HRESULT QueryContextMenu(IntPtr hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
[PreserveSig()]
new HRESULT InvokeCommand(ref CMINVOKECOMMANDINFO pici);
[PreserveSig()]
new HRESULT GetCommandString(uint idCmd, uint uType, IntPtr pReserved, StringBuilder pszName, uint cchMax);
[PreserveSig()]
HRESULT HandleMenuMsg(uint uMsg, int wParam, IntPtr lParam);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CMINVOKECOMMANDINFO
{
public int cbSize;
public int fMask;
public IntPtr hwnd;
public IntPtr lpVerb;
public IntPtr lpParameters;
public IntPtr lpDirectory;
public int nShow;
public int dwHotKey;
public IntPtr hIcon;
}
public const int CMF_NORMAL = 0x00000000;
public const int CMF_DEFAULTONLY = 0x00000001;
public const int CMF_VERBSONLY = 0x00000002;
public const int CMF_EXPLORE = 0x00000004;
public const int CMF_NOVERBS = 0x00000008;
public const int CMF_CANRENAME = 0x00000010;
public const int CMF_NODEFAULT = 0x00000020;
public const int CMF_INCLUDESTATIC = 0x00000040;
public const int CMF_ITEMMENU = 0x00000080;
public const int CMF_EXTENDEDVERBS = 0x00000100;
public const int CMF_DISABLEDVERBS = 0x00000200;
public const int CMF_ASYNCVERBSTATE = 0x00000400;
public const int CMF_OPTIMIZEFORINVOKE = 0x00000800;
public const int CMF_SYNCCASCADEMENU = 0x00001000;
public const int CMF_DONOTPICKDEFAULT = 0x00002000;
public const int CMF_RESERVED = unchecked((int)0xffff0000);
public const int SW_SHOWNORMAL = 1;
public const int GCS_VERBA = 0x00000000; // canonical verb
public const int GCS_HELPTEXTA = 0x00000001; // help text (for status bar)
public const int GCS_VALIDATEA = 0x00000002; // validate command exists
public const int GCS_VERBW = 0x00000004; // canonical verb (unicode)
public const int GCS_HELPTEXTW = 0x00000005; // help text (unicode version)
public const int GCS_VALIDATEW = 0x00000006; // validate command exists (unicode)
public const int GCS_VERBICONW = 0x00000014; // icon string (unicode)
public const int GCS_UNICODE = 0x00000004; // for bit testing - Unicode string
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr CreatePopupMenu();
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint TrackPopupMenu(IntPtr hMenu, uint uFlags, int x, int y, int nReserved, IntPtr hWnd, IntPtr prcRect);
public const int TPM_LEFTBUTTON = 0x0000;
public const int TPM_RIGHTBUTTON = 0x0002;
public const int TPM_LEFTALIGN = 0x0000;
public const int TPM_CENTERALIGN = 0x0004;
public const int TPM_RIGHTALIGN = 0x0008;
public const int TPM_TOPALIGN = 0x0000;
public const int TPM_VCENTERALIGN = 0x0010;
public const int TPM_BOTTOMALIGN = 0x0020;
public const int TPM_HORIZONTAL = 0x0000; /* Horz alignment matters more */
public const int TPM_VERTICAL = 0x0040; /* Vert alignment matters more */
public const int TPM_NONOTIFY = 0x0080; /* Don't send any notification msgs */
public const int TPM_RETURNCMD = 0x0100;
public const int TPM_RECURSE = 0x0001;
public const int TPM_HORPOSANIMATION = 0x0400;
public const int TPM_HORNEGANIMATION = 0x0800;
public const int TPM_VERPOSANIMATION = 0x1000;
public const int TPM_VERNEGANIMATION = 0x2000;
public const int TPM_NOANIMATION = 0x4000;
public const int TPM_LAYOUTRTL = 0x8000;
public const int TPM_WORKAREA = 0x10000;
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, [In, Out] ref MENUITEMINFO lpmii);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct MENUITEMINFO
{
public uint cbSize;
public uint fMask;
public uint fType; // used if MIIM_TYPE (4.0) or MIIM_FTYPE (>4.0)
public uint fState; // used if MIIM_STATE
public uint wID; // used if MIIM_ID
public IntPtr hSubMenu; // used if MIIM_SUBMENU
public IntPtr hbmpChecked; // used if MIIM_CHECKMARKS
public IntPtr hbmpUnchecked; // used if MIIM_CHECKMARKS
public IntPtr dwItemData; // used if MIIM_DATA
public IntPtr dwTypeData;
//[MarshalAs(UnmanagedType.LPWStr)]
//public string dwTypeData; // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
public uint cch; // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
public IntPtr hbmpItem; // used if MIIM_BITMAP
}
public const int MIIM_STATE = 0x00000001;
public const int MIIM_ID = 0x00000002;
public const int MIIM_SUBMENU = 0x00000004;
public const int MIIM_CHECKMARKS = 0x00000008;
public const int MIIM_TYPE = 0x00000010;
public const int MIIM_DATA = 0x00000020;
public const int MIIM_STRING = 0x00000040;
public const int MIIM_BITMAP = 0x00000080;
public const int MIIM_FTYPE = 0x00000100;
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int GetMenuString(IntPtr hMenu, uint uIDItem, StringBuilder lpString, int cchMax, uint flags);
public const int MF_BYCOMMAND = 0x00000000;
public const int MF_BYPOSITION = 0x00000400;
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int GetMenuItemCount(IntPtr hMenu);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool DestroyMenu(IntPtr hMenu);