Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Invoking directly ContextMenu of Files and Folders


  • Please log in to reply
62 replies to this topic
  • Guests
  • Last active:
  • Joined: --
sorry for a no0b question
I know this script is useful but how exactly do I use it?
I mean I know extract the file and run it,
but it shows the context menu only once, per time I launch the script?

is there any key to launch the context menu?
and how is this better than the regular context menu?

please reply.

Eucaly61
  • Members
  • 18 posts
  • Last active: Feb 03 2010 10:07 AM
  • Joined: 16 May 2009

... how exactly do I use it? ...

Trigger from hotkey, or as a portion of a large script ....
Do not limit your imagination (and also innovation)

...how is this better than the regular context menu?

the ContextMenu is registed under your AutoHotKey thread, thus you can do some pre-process and/or post-process as you like.

inspired from this, I make a varient that get and invoke menu items without bring up popup menu

I will show that in next post
Eucaly61's DIY World (Mainly in Chinese)
http://eucaly61.blog...abel/AutoHotKey

Eucaly61
  • Members
  • 18 posts
  • Last active: Feb 03 2010 10:07 AM
  • Joined: 16 May 2009
.
below is the demo code to get and invoke ContextMenu (ShellEx) items without bring up popup menu

(dependency: CoHelper.ahk)

#SingleInstance Force
SetBatchLines,-1

#Include CoHelper.ahk

; FileName : ShellExt-Demo-008.ahk 

sIId = {000214E4-0000-0000-C000-000000000046}	; IContextMenu

;sDll := "igfxpph.dll"
sClsId1 = {3AB1675A-CCFF-11D2-8B20-00A0C93CB1F4} igfx Shellext

;sDll := "wmpshell.dll"
sClsId2 = {F1B9284F-E9DC-4e68-9D7E-42362A59F0FD} add to playlist
; => could see menu, but did not try more details 

;sDll := "shdocvw.dll"
sClsId3 = {2559a1f0-21d7-11d4-bdaf-00c04f60b9f0} findfiles

;sDll := "shdocvw.dll"
sClsId4 = {2559a1f1-21d7-11d4-bdaf-00c04f60b9f0} help

;sDll := "SHELL32.dll"
sClsId5 = {D969A300-E7FF-11d0-A93B-00A0C90F2719} New ...
; => not able to explore 2nd level, maybe it's context sensitive

sClsId6 = {7BA4C740-9E81-11CF-99D3-00AA004AE837} send-to
; => not able to explore 2nd level, maybe it's context sensitive

; You could add more CLSID (clsid7, 8, 9 and so on), 
	; only those enclosed by {...} are recognized as CLSID 

Gui, Font, S12
Gui, Add, ListBox, w500 r10 vClsIdList
Gui, Add, Button, ,Go

lst=
Loop
{
	if !sClsId%A_index%
		break
	lst .= "|" . sClsId%A_index%
}

GuiControl,, ClsIdList, %lst%
Gui, SHow
return

GuiClose:
	ExitApp

ButtonGo:
	Gui, Submit, noHide
	p1 := RegExMatch(ClsIdList,"(?P<ClsId>\{[^\}]*\})",s)

	if !p1
		return

	Critical
	CoInitialize()

	pcm := CreateObject(sClsId,sIId)
	if !pcm 
		return

l := ClsIdList . "`n`n"

	hMenu := DllCall("CreatePopupMenu")
	DllCall(VTable(pcm,3), "Uint", pcm 
			,"Uint", hMenu, "Uint", 0
			, "Uint", 3, "Uint", 0x7FFF, "Uint", 0)   ; QueryContextMenu

l .= "idx / idn / MenuItem / hSubMenu`n"
l .= "========================================`n" 

	Loop, % DllCall( "GetMenuItemCount", UInt,hMenu ) {
		idx := A_Index-1
		idn := DllCall( "GetMenuItemID", UInt,hMenu, Int,idx )
		nSize := DllCall( "GetMenuString"
				, UInt,hMenu, Int,idx, Int,0, Int,0, UInt,0x400 ) + 1
		VarSetCapacity( mStr,nSize )
		DllCall( "GetMenuString"
		 		, UInt,hMenu, Int,idx, Str,mStr, Int,nSize, UInt,0x400 )

		hSub := DllCall("GetSubMenu",Uint,hMenu,int,idx)

l .= idx . " / " . idn . " / " . mstr . " / " . hSub . "`n" 

		if hSub
		{
l .= "--------------------`n" 
			Loop, % DllCall( "GetMenuItemCount", UInt,hSub ) {
				idx := A_Index-1
				idn := DllCall( "GetMenuItemID", UInt,hSub, Int,idx )
				nSize := DllCall( "GetMenuString"
						, UInt,hSub, Int,idx, Int,0, Int,0, UInt,0x400 ) + 1
				VarSetCapacity( mStr1,nSize )
				DllCall( "GetMenuString"
			 		, UInt,hSub, Int,idx, Str,mStr1, Int,nSize, UInt,0x400 )

				hSub1 := DllCall("GetSubMenu",Uint,hSub,int,idx)
		
l .= "[" . idx . "] / " . idn . " / " . mstr . " -- " . mstr1 . " / " . hSub1 . "`n" 
			}
l .= "--------------------`n" 
		} 
	} 

msgbox, % l


; implement below to invoke menu command with given idn
if (0) {
	DetectHiddenWindows, On
	Process, Exist
	WinGet, hAHK, ID, ahk_pid %ErrorLevel%

;	idn := .....

	DllCall("GetCursorPos", "int64P", pt)

	NumPut(VarSetCapacity(ici,64,0),ici), NumPut(0x4000|0x20000000,ici,4), NumPut(1,NumPut(hAHK,ici,8),12), NumPut(idn-3,NumPut(idn-3,ici,12),24), NumPut(pt,ici,56,"int64")

	DllCall(VTable(pcm,4), "Uint", pcm, "Uint", &ici)   ; InvokeCommand
}

	DllCall("DestroyMenu", "Uint", hMenu)
	Release(pcm)
	CoUninitialize()
	Critical, Off
	return

Thanks for the suggestion from Sean, now DllGetClassObject (the first two lines)

[color=orange]* DllGetClassObject with IID_IClassFactory
* pcm := CreateInstance with "{000214E4-0000-0000-C000-000000000046}"	; IContextMenu[/color]
* hMenu := DllCall("CreatePopupMenu")

are replaced by
pcm := CreateObject(CLSID,IID)

more shellex (or contextmenu) related CLSID and DLL could be found under below registry key
HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers
HKEY_CLASSES_ROOT\{file type or CLSID}\shellex\ContextMenuHandlers
HKEY_CLASSES_ROOT\Directory\shellex\ContextMenuHandlers
HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers
or even more in <!-- m -->http://windowsxp.mvp... ... irectories<!-- m -->
.
.
as for the magic number
DllCall(NumGet(NumGet(1*psf)[color=red]+32[/color]) ... 
DllCall(NumGet(NumGet(1*psf)[color=blue]+40[/color]) ...

... have to refer to header files for that. In this case of IShellFolder, for example, the infos are contained in ShObjIdl.h and/or ShObjIdl.idl.

There are on-line document to refer (for example,

CreateViewObject and GetUIObjectOf under interface IShellFolder : IUnknown
or QueryContextMenu under interface IContextMenu : IUnknown

or

http://www.koders.com/noncode/fid66A0E1FAB1C94FB665CFA5236DA4CDAEA22742CE.aspx#L1051


Eucaly61's DIY World (Mainly in Chinese)
http://eucaly61.blog...abel/AutoHotKey

Eucaly61
  • Members
  • 18 posts
  • Last active: Feb 03 2010 10:07 AM
  • Joined: 16 May 2009
I don't do further study, but I guess this would be helpful if some CLSID / DLL are not registed in registry key

that is, directly DllGetClassObject from DLL,

you could replace
pcm := CreateObject(sClsId,sIId)
with
sDll := "some.dll"
If (GUID4String(sbinClassId, sClsId) And GUID4String(sbinIId, sIId))
	pcm := COM_CreateInstanceFromDll(sDll,sbinClassId,sbinIId)
else
	return
and, add below function define (a subset modified from ws4ahk.ahk)
COM_CreateInstanceFromDll(sDll, ByRef sbinClassId, ByRef sbinIId)
{
	static IID_IClassFactory := "{00000001-0000-0000-C000-000000000046}"

	if (!GUID4String(sbinIID_IClassFactory, IID_IClassFactory))
		return
	
	If (!Unicode4Ansi(wsDll, sDll))
		return 		

	hDll := DllCall("ole32\CoLoadLibrary", "Str", wsDll, "Int", 1, "UInt")

	If (ErrorLevel <> 0) or (hDll = 0)
		Return

	iErr := DllCall(sDll . "\DllGetClassObject"
					,"Str" , sbinClassId
					,"Str" , sbinIID_IClassFactory
					,"UInt*", pIFactory
					,"Int")
	If iErr
		Return
	
	iObjPtr := COM_IClassFactory_CreateInstance(pIFactory, 0, sbinIId)
	
	Release (pIFactory)
	
	Return iObjPtr
}


COM_IClassFactory_CreateInstance(ppvIClassFactory, pUnkOuter, ByRef riid)
{
	iErr := DllCall(VTable(ppvIClassFactory, 3), "UInt", ppvIClassFactory
					, "UInt",  pUnkOuter
					, "Str",   riid
					, "Uint*", ppvObject
					, "Int")
	
	If iErr
		Return
	
	Return ppvObject
}

Eucaly61's DIY World (Mainly in Chinese)
http://eucaly61.blog...abel/AutoHotKey

  • Guests
  • Last active:
  • Joined: --
@Eucaly61

I really appriciate that you cared to reply.
however I'm yet confused,
I tried to add ^1:: as hotkey but it gives me error. that { is not complete.

all I want is
say when my mouse has selected a file / folder
and I press something like tilde key or Insert key then
it launches this Sean's Context Menu for that file/folder
how do I do it?

Thanks in Advance

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

below is the demo code to get and invoke ContextMenu (ShellEx) items without bring up popup menu

Nice. Although I suppose it still may not be able to retrieve all the items of some submenus without bring up the pop-up menu, it can be used as an extension of InvokeVerb through COM. BTW, the GetMenu routine is familiar to me.

joebodo
  • Members
  • 48 posts
  • Last active: Feb 13 2010 09:55 PM
  • Joined: 28 Apr 2008
Thanks for the information on getting and executing menu selections. I have been looking for this for awhile.

I am able to bring up the shell context menu for a file or a folder and execute the command with just a few problems.

I am unable to iterate through the "Send To" or the "Open With" submenus. I can get a handle to the submenu, but they don't appear to be populated.

When I select copy, the file is not being copied into the clipboard (appears that nothing happens as the clipboard has the text from before selecting that option).

I'm going to post my code as a reference for others:

The "menu" variable that I am using is for my own menu handling - hope it is not too confusing.

ShellContextMenu(menu, fileName) {

	CoInitialize()

	DllCall("shell32\SHParseDisplayName", "Uint", Unicode4Ansi(wPath,fileName)
		, "Uint", 0, "UintP", pidl, "Uint", 0, "Uint", 0)

	DllCall("shell32\SHBindToParent", "Uint", pidl
		, "Uint", GUID4String(IID_IShellFolder,"{000214E6-0000-0000-C000-000000000046}")
		, "UintP", psf, "UintP", pidlChild)
	DllCall(NumGet(NumGet(1*psf)+40), "Uint", psf
		, "Uint", 0, "Uint", 1, "UintP", pidlChild
		, "Uint", GUID4String(IID_IContextMenu,"{000214E4-0000-0000-C000-000000000046}")
		, "Uint", 0, "UintP", pcm)

	Release(psf)
	CoTaskMemFree(pidl)

	; Create a stub menu
	hMenu := DllCall("CreatePopupMenu")

	; Get the context menu
	DllCall(NumGet(NumGet(1*pcm)+12), "Uint", pcm, "Uint", hMenu, "Uint", menuNo, "Uint", 3, "Uint", 0x7FFF, "Uint", 0)

	menuAddSeparator(menu)
	addMenuEntries(hMenu, menu, fileName)
}

addMenuEntries(hMenu, menu, fileName) {

	Loop, % DllCall( "GetMenuItemCount", UInt,hMenu ) {
	
		idx := A_Index-1
		idn := DllCall( "GetMenuItemID", UInt,hMenu, Int,idx )
		nSize := DllCall( "GetMenuString"
		        , UInt,hMenu, Int,idx, Int,0, Int,0, UInt,0x400 ) + 1
		VarSetCapacity( mStr,nSize )
		DllCall( "GetMenuString"
		         , UInt,hMenu, Int,idx, Str,mStr, Int,nSize, UInt,0x400 )
		
		if (mstr <> "" AND idn = -1) {
			; logA("submenu:" . hSub)
			; hSub := DllCall("GetSubMenu",Uint,hMenu,int,idx)

; Not functioning properly...
			; addMenuEntries(hSub, A_Args, mstr, idx, pcm)
		} else if (mstr = "") {
			menuAddSeparator(menu)
		} else if (idn != -1) {
			command := commandCreate("ContextMenu Run", "/file:" . fileName . " /idn:" . idn)
			menuAdd(menu, "/item:" . mstr, command)
		} else {
			menuAddSeparator(menu)
		}
	}
}


Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
As I said before, if the menu item appears in the main menu, not in the submenu, InvokeVerb is simpler to use.
sPath := A_ScriptFullPath ; path of the target file

SplitPath, sPath, sName, sDir
COM_Init()
psh := COM_CreateObject("Shell.Application")
COM_Invoke(psh, "NameSpace[" sDir "].ParseName[" sName "].InvokeVerb", "Copy")
COM_Release(psh)
COM_Term()
MsgBox, DONE


skrommel
  • Members
  • 193 posts
  • Last active: Jun 07 2010 08:30 AM
  • Joined: 30 Jul 2004

Has anyone managed to expand on this to work with a set of files? If so, how did you mange it?

How do you think Explorer does it? You can exactly do the same with the script.


:( I'm having the same problem... Anyone know how to get this working with multiple files?

Skrommel

skwire
  • Moderators
  • 279 posts
  • Last active: Aug 12 2014 05:16 PM
  • Joined: 18 Jan 2006
Sean, would it be possible to provide an example showing how to make this work with with a multiple file selection, please?

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
All are described here. Notice that the function accepts an array of (child) pidls, meaning that you can pass an array of multiple child pidls instead of (an array of) single child pidl. That's what explorer does.

temp01
  • Members
  • 120 posts
  • Last active: May 18 2013 08:27 PM
  • Joined: 09 Jul 2009
Context menu for multiple items (files/folders):
#Persistent

ShellContextMenu("", "0x000D; 0x0027")	; CSIDL_MYMUSIC; CSIDL_MYPICTURES
ShellContextMenu("C:\Windows\Media", "notify.wav; flourish.mid")
;ShellContextMenu("C:\Windows", "system32\; Cursors\; Media\")
Return

ShellContextMenu(parentDir, files){
	COM_CoInitialize()
	COM_GUID4String(IID_IShellFolder,"{000214E6-0000-0000-C000-000000000046}"), COM_GUID4String(IID_IContextMenu,"{000214E4-0000-0000-C000-000000000046}")
	
	parentDir := RegExReplace(parentDir, "\\$"), i:=0
	Loop, Parse, files, `;, %A_Space%%A_Tab%
	{
		If A_LoopField is Integer
			i++, DllCall("shell32\SHGetFolderLocation", "Uint", 0, "int", A_LoopField, "Uint", 0, "Uint", 0, "UintP", pidl%i%)
		Else IfNotExist, %parentDir%\%A_LoopField%
			Continue
		Else
			i++, DllCall("shell32\SHParseDisplayName", "Uint", COM_Unicode4Ansi(wPath,parentDir "\" A_LoopField), "Uint", 0, "UintP", pidl%i%, "Uint", 0, "Uint", 0)
		DllCall("shell32\SHBindToParent", "Uint", pidl%i%, "Uint", &IID_IShellFolder, "UintP", psf%i%, "UintP", pidlChild%i%)
	}
	IfEqual,i,0, return
	
	VarSetCapacity(apidl, i * 4, 0)
	Loop, %i%
		NumPut(pidlChild%A_Index%, apidl, (A_Index-1)*4, "UInt")
	
	DllCall(NumGet(NumGet(1*psf1)+40), "Uint", psf1, "Uint", 0, "Uint", i, "Uint", &apidl, "Uint", &IID_IContextMenu, "Uint", 0, "UintP", pcm)
	Loop, %i%
		COM_Release(psf%A_Index%), COM_CoTaskMemFree(pidl%A_Index%)
	
	hMenu := DllCall("CreatePopupMenu")
	DllCall(NumGet(NumGet(1*pcm)+12), "Uint", pcm, "Uint", hMenu, "Uint", 0, "Uint", 3, "Uint", 0x7FFF, "Uint", 0)   ; QueryContextMenu
	DetectHiddenWindows, On
	Process, Exist
	WinGet, hAHK, ID, ahk_pid %ErrorLevel%
	WinActivate, ahk_id %hAHK%
	Global   pcm2 := COM_QueryInterface(pcm,IID_IContextMenu2:="{000214F4-0000-0000-C000-000000000046}")
	Global   pcm3 := COM_QueryInterface(pcm,IID_IContextMenu3:="{BCFCE0A0-EC17-11D0-8D10-00A0C90F2719}")
	Global   WPOld:= DllCall("SetWindowLong", "Uint", hAHK, "int",-4, "int",RegisterCallback("WindowProc"))
	DllCall("GetCursorPos", "int64P", pt)
	DllCall("InsertMenu", "Uint", hMenu, "Uint", 0, "Uint", 0x0400|0x800, "Uint", 2, "Uint", 0)
	DllCall("InsertMenu", "Uint", hMenu, "Uint", 0, "Uint", 0x0400|0x002, "Uint", 1, "Uint", &parentDir)
	idn := DllCall("TrackPopupMenu", "Uint", hMenu, "Uint", 0x0100, "int", pt << 32 >> 32, "int", pt >> 32, "Uint", 0, "Uint", hAHK, "Uint", 0)
	NumPut(VarSetCapacity(ici,64,0),ici), NumPut(0x4000|0x20000000,ici,4), NumPut(1,NumPut(hAHK,ici,8),12), NumPut(idn-3,NumPut(idn-3,ici,12),24), NumPut(pt,ici,56,"int64")
	DllCall(NumGet(NumGet(1*pcm)+16), "Uint", pcm, "Uint", &ici)   ; InvokeCommand
	;   VarSetCapacity(sName,259), DllCall(NumGet(NumGet(1*pcm)+20), "Uint", pcm, "Uint", idn-3, "Uint", 1, "Uint", 0, "str", sName, "Uint", 260)   ; GetCommandString
	DllCall("GlobalFree", "Uint", DllCall("SetWindowLong", "Uint", hAHK, "int", -4, "int", WPOld))
	DllCall("DestroyMenu", "Uint", hMenu)
	COM_Release(pcm3)
	COM_Release(pcm2)
	COM_Release(pcm)
	COM_CoUninitialize()
	pcm2:=pcm3:=WPOld:=0
}

WindowProc(hWnd, nMsg, wParam, lParam)
{
   Critical
   Global   pcm2, pcm3, WPOld
   If   pcm3
   {
      If   !DllCall(NumGet(NumGet(1*pcm3)+28), "Uint", pcm3, "Uint", nMsg, "Uint", wParam, "Uint", lParam, "UintP", lResult)
         Return   lResult
   }
   Else If   pcm2
   {
      If   !DllCall(NumGet(NumGet(1*pcm2)+24), "Uint", pcm2, "Uint", nMsg, "Uint", wParam, "Uint", lParam)
         Return   0
   }
   Return   DllCall("user32.dll\CallWindowProcA", "Uint", WPOld, "Uint", hWnd, "Uint", nMsg, "Uint", wParam, "Uint", lParam)
}


Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
Nice try, but too much redundancy here, dig a little further. And ; as the delimiter is not a good choice as ; is a valid character in a file name. What I'm using, for example, is like this.
ShellContextMenu("C:\Windows\test1.txt[color=red]|[/color]test2.txt[color=red]|[/color]test3.txt")


HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Really great function, however it looks like "Copy" does not work, possibly due to absence of Explorer and marked file :(
Could we redirect the function to script, to do the job?

Gauss
  • Members
  • 203 posts
  • Last active: Jan 27 2012 12:49 PM
  • Joined: 10 Sep 2009
The link for CoHelper.ahk is dead