I was wondering if it is possible (I'm sure it is) to add an item to the right click menu when running file explorer.  For instance,  several jpg files will be selected,  the user right clicks, the menu pops up and says "change size", they click on that and my program retrieves the file names and modifies the files.  Is this possible?
Thanks,
Clint
p.s. This may be a question for sapero  :)
			
			
			
				Hi, there are several variants, and this is more registry based instalation.
1. Root key (later named as ROOT)
a) For all users: (single installation)
ROOT = HKEY_CLASSES_ROOT (this key merges two following keys, writing to HKCR is redirected to HKLM if the subkey part does not exists in HKCU\...\classes)
or
ROOT = HKEY_LOCAL_MACHINE\SOFTWARE\Classes
b) For current user (separate installation for each user account)
ROOT = HKEY_CURRENT_USER\Software\Classes
2. Subkey:
a) Read the default registry value from ROOT\.jpg    There will be "jpgfile", or "ACDC_JPG" (without quotes) if you have installed ACDSee (very old 32)
b) create a new key ROOT\X\shell\Y\command, where X is the value from ROOT\.jpg, and Y is the verb you want to add to context menu: Resize
c) in this new key, set the default value to the full path to your program (quoted if contains spaces) with quoted %1 after a space
"c:\my programs\resizer.exe" "%1"
or c:\resizer.exe "%1"
B and C can be done in a single step: just set the value in that key. This key will be automatically created. Use REGGETSTRING, or SHSetValue from shlwapi.inc.
The first variant is ready to use. After installing the keys, optionally press F5 after focusing your shell (desktop), or call a special shell function SHChangeNotify, to ensure that the new verb will be loaded.
Each selected file will execute a new instance of your program, with a parameter:
...\resizer.exe "full path.jpg"
If you want to pass all selected files automatically to single program, use some interprocess communication (like SETCONTROLTEXT/EM_SETTEXT/EM_REPLACESEL + @ENCHANGE, or even a pipe/socket server, WM_COPYDATA message), or create additional DDE subkeys in the registry (solution at request).
Second variant: if you select .jpg and other file types, the first solution will be not working. For this case you could install your program in ROOT\*\shell\Y\command (default value), or create a shell extension (with 3 COM based classes: IClassFactory, IShellExtInit and IContextMenu).
The following installer part is taken from my custom made jpg-resizer, designed for XP (HKCR key). The two doubles from command line are used to divide image width and height:
sub install()
	istring jpgfile[256]
	int iType, size = 256
	if (!SHGetValue(HKEY_CLASSES_ROOT, ".jpg", NULL, &iType, &jpgfile, &size))
		lstrcat(jpgfile, "\\shell\\")
		pointer verb = &jpgfile + lstrlen(jpgfile)
		istring commandline[MAX_PATH+32]
		GetModuleFileName(0, &commandline, MAX_PATH)
		PathQuoteSpaces(commandline)
		pointer args = &commandline + lstrlen(commandline)
		wsprintf(verb, "# Resize to 10%%\\command")
		wsprintf(args, " \"%%1\" 10.0 10.0")
		SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))
		wsprintf(verb, "# Resize to 20%%\\command")
		wsprintf(args, " \"%%1\" 5.0 5.0")
		SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))
		wsprintf(verb, "# Resize to 30%%\\command")
		wsprintf(args, " \"%%1\" 3.3 3.3")
		SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))
		wsprintf(verb, "# Resize to 40%%\\command")
		wsprintf(args, " \"%%1\" 2.5 2.5")
		SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))
		wsprintf(verb, "# Resize to 50%%\\command")
		wsprintf(args, " \"%%1\" 2.0 2.0")
		SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))
		wsprintf(verb, "# Resize to 60%%\\command")
		wsprintf(args, " \"%%1\" 1.6 1.6")
		SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))
		wsprintf(verb, "# Resize to 70%%\\command")
		wsprintf(args, " \"%%1\" 1.42 1.42")
		SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))
	endif
	MessageBoxA(0,"Instalation completed for .jpg","",MB_ICONINFORMATION)
endsub
			
			
			
				Thanks, sapero.  Will have a look at it.  I'll be back if I have questions.
Clint
			
			
			
				Sapero,
Could you give me an example of what you mean by this or explain it a little more?  I have the single instance part working.
QuoteIf you want to pass all selected files automatically to single program, use some interprocess communication (like setcontroltext + @enchange)
Thanks,
Clint
			
				ckoehn, this is the complete example:
On startup, it will register a temporary verb in the shell menu. The first instance will open a window. Each secondary instance will check for "already running", trying to find the window from first instance (for internal communication).
$define CAPTION - this can be any string, but it must be system (or session) wide unique.
$include "shlwapi.inc"
enum controls
	IDC_REMOTEFILEPATH = 1000
	IDC_UNREGISTER
endenum
DIALOG d1
' option 1: the caption of main window must be constant
$define CAPTION             "{ba644e29-f4f6-4876-b287-5a7d21522cf0}"
$define SINGLEINSTANCEMUTEX "{aa45a30d-86e0-4ac8-82dd-44793cebf08b}"
$define FILELOCKMUTEX       "{3117e49e-4d2e-455b-a251-9eefe31d7355}"
class CSingleInstance
	declare CSingleInstance()
	declare _CSingleInstance()
	declare RegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
	declare UnRegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
	declare IsAlreadyRunning(),BOOL ' call once! CAPTION,SINGLEINSTANCEMUTEX
	' use before and after processing new file
	declare AcquireFileLock()   ' FILELOCKMUTEX
	declare UnAcquireFileLock() ' FILELOCKMUTEX
	HANDLE m_hSingleInstanceMutex
	HANDLE m_hFileLockMutex
endclass
' single instance; mutex name is generated from CoCreateGuid+StringFromCLSID2
' GUID g
' iwstring wszGuid[42]
' CoCreateGuid(&g)
' StringFromCLSID2(&g, &wszGuid, 42)
' print(w2s(wszGuid))
CSingleInstance csi
csi.RegisterShellMenuVerb("# Temporary IWBasic Verb", ".jpg", TRUE)
if (csi.IsAlreadyRunning())
	if (SendCommandLineToFirstInstance(CAPTION, IDC_REMOTEFILEPATH))
		end
	endif
	' the first instance window was not found, it can open slowly or close
	' TODO: create here a loop - ex. 10 tries in a second
	' for ...
		Sleep(500)
		if (csi.m_hSingleInstanceMutex and !WaitForSingleObject(csi.m_hSingleInstanceMutex, 0))
			ReleaseMutex(csi.m_hSingleInstanceMutex)
			' first instance terminated. Jump over SendCommandLineToFirstInstance and create main window
		endif
		if (SendCommandLineToFirstInstance(CAPTION, IDC_REMOTEFILEPATH))
			end
		endif
	' next ...
endif
CREATEDIALOG d1,0,0,369,221,0x80CA0080,0,CAPTION,&d1_handler
CONTROL d1,@EDIT,"",3,199,70,20,0x40880080,IDC_REMOTEFILEPATH
CONTROL d1,@BUTTON,"Unregister shell menu verb",132,137,150,20,0x50000000,IDC_UNREGISTER
DOMODAL d1
end
sub SendCommandLineToFirstInstance(string szCaption, int idControl),BOOL
	int argc, a
	pointer argv, env
	BOOL result = TRUE
	$undeclare __GetMainArgs
	declare cdecl extern __GetMainArgs(pointer argc, pointer ppargv, pointer ppenv, int expand_wildcards)
	__GetMainArgs(&argc, &argv, &env, FALSE)
	' quit if command line parameters are not provided
	if (argc > 1)
		result = FALSE ' assume failure in FindWindow and GetDlgItem
		' find main window
		HWND hwndMainWindow = FindWindow(NULL, szCaption)
		if (hwndMainWindow)
			' find the edit control
			HWND hwndEdit = GetDlgItem(hwndMainWindow, idControl)
			if (hwndEdit)
				SetForegroundWindow(hwndMainWindow)
				if (IsIconic(hwndMainWindow)) then _ShowWindow(hwndMainWindow, SW_RESTORE)
				' acquire exclusive access to edit control
				csi.AcquireFileLock()
				' forward all command line arguments to edit control
				for a=1 to argc-1
					' select all
					SENDMESSAGE hwndEdit, EM_SETSEL, 0, -1
					' replace selection - this will execute @ENCHANGE handler
					SENDMESSAGE hwndEdit, EM_REPLACESEL, TRUE, *<LPSTR>argv[a]
				next a
				csi.UnAcquireFileLock()
				result = TRUE
			endif
		endif
	endif
	return result
endsub
SUB d1_handler(),int
	SELECT @MESSAGE
		CASE @IDINITDIALOG
			CENTERWINDOW d1
			' TODO: Initialize any controls here
$ifdef PARSE_CMD_LINE_IN_ONINITDIALOG
			ParseCommandLine()
$else
			' WM_USER is posted not only from user code,
			' so we should initialize lParam and wParam to unique values.
			PostMessage(d1.hWnd, WM_USER, &d1_handler, @IDINITDIALOG)
$endif
		
		CASE WM_USER
			if ((@WPARAM=&d1_handler) and (@LPARAM=@IDINITDIALOG))
				ParseCommandLine()
			' else WM_USER was send from other code part
			endif
		CASE @IDCLOSEWINDOW
			CLOSEDIALOG d1,@IDOK
		CASE @IDCONTROL
			select @WPARAM
				case (IDC_REMOTEFILEPATH | (@ENCHANGE<<16))
					' read the path from edit IDC_REMOTEFILEPATH
					MESSAGEBOX d1, GETCONTROLTEXT(d1, IDC_REMOTEFILEPATH), "File"
				case IDC_UNREGISTER
					EnableWindow(@LPARAM, FALSE)
					csi.UnRegisterShellMenuVerb("# Temporary IWBasic Verb", ".jpg", TRUE)
			endselect
			'SELECT @CONTROLID
			'ENDSELECT
	ENDSELECT
	RETURN 0
ENDSUB
sub ParseCommandLine()
	int a, argc
	pointer argv, env, pszFile
	__GetMainArgs(&argc, &argv, &env, FALSE)
	if (argc > 1)
		csi.AcquireFileLock()
		for a=1 to argc-1
			pszFile = *<LPSTR>argv[a]
			' TODO: process file *<string>pszFile
			MESSAGEBOX d1, *<string>pszFile, "File"
		next a
		csi.UnAcquireFileLock()
	endif
endsub
sub CSingleInstance::CSingleInstance()
	m_hSingleInstanceMutex = 0
	m_hFileLockMutex       = 0
endsub
sub CSingleInstance::_CSingleInstance()
	if (m_hSingleInstanceMutex) then CloseHandle(m_hSingleInstanceMutex)
	UnAcquireFileLock()
	if (m_hFileLockMutex) then CloseHandle(m_hFileLockMutex)
endsub
sub CSingleInstance::AcquireFileLock()
	if (!m_hFileLockMutex)
		m_hFileLockMutex = CreateMutex(0, FALSE, FILELOCKMUTEX)
	endif
	WaitForSingleObject(m_hFileLockMutex, INFINITE)
endsub
sub CSingleInstance::UnAcquireFileLock()
	if (m_hFileLockMutex)
		ReleaseMutex(m_hFileLockMutex)
	endif
endsub
sub CSingleInstance::IsAlreadyRunning(),BOOL
	BOOL result = FALSE
	' pass FALSE as the second parameter to CreateMutex
	' because we don't need to own this mutex (don't wait for ownership)
	m_hSingleInstanceMutex = CreateMutex(0, FALSE, SINGLEINSTANCEMUTEX)
	DWORD dwLastError = GetLastError()
	if (m_hSingleInstanceMutex)
		if (dwLastError = ERROR_ALREADY_EXISTS) ' already running
			result = TRUE
		endif
	else
		' CreateMutex failed
		if (dwLastError = ERROR_ACCESS_DENIED) ' already running under different credentials (runas)
			result = TRUE
			' OpenMutex should succeed, but we don't need it
		endif
	endif
	return result
endsub
sub CSingleInstance::RegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
	istring szRegPath[256]
	istring commandline[MAX_PATH+8]
	int iType, size = 256
	DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
	if (SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, &iType, &szRegPath, &size))
		' szFileType (.jpg) is probably not yet registered in HKEY_CLASSES_ROOT.
		' If so, SHGetValue will return ERROR_PATH_NOT_FOUND error code.
		SHSetValue(hkRoot, "Software\\Classes\\jpg", NULL, REG_SZ, "jpgfile", 7)
		lstrcpy(&szRegPath, "jpgfile")
	endif
	' generate command line: "c:\program files\my stuff\jpg resizer.exe" "%1"
	GetModuleFileName(0, &commandline, MAX_PATH)
	PathQuoteSpaces(commandline)
	lstrcat(commandline, " \"%1\"")
	pointer p = AllocHeap(len(szRegPath) + len(szVerb) + 33) ' 33 = 1+len("Software\\Classes\\\\shell\\\\command")
	if (p)
		wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\command", szRegPath, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, commandline, len(commandline))
		FreeHeap(p)
	endif
endsub
sub CSingleInstance::UnRegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
	istring szRegPath[256]
	int iType, size = 256
	DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
	if (!SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, &iType, &szRegPath, &size))
		pointer p = AllocHeap(len(szRegPath) + len(szVerb) + 33)
		if (p)
			wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\command", szRegPath, szVerb)
			SHDeleteKey(hkRoot, p)
			wsprintf(p, "Software\\Classes\\%s\\shell\\%s", szRegPath, szVerb)
			SHDeleteKey(hkRoot, p)
			FreeHeap(p)
		endif
	endif
endsub
Added (Un)AcquireFileLock methods, for mutual initialization. Example: application is not running. You do select two images in explorer, and select the new verb from context menu. Two instances of this program are created, one of them is always first (IsAlreadyRunning returns false), so it opens the main window, calls ParseCommandLine where it acquires the lock (AcquireFileLock method) and processes the command line, showing a message box with image path. The second instance hungs at AcquireFileLock (inside SendCommandLineToFirstInstance subroutine), waiting for the first instance to release a mutex, what happens at the end of ParseCommandLine, so only single message box is displayed at a time.
			
			
			
				Thanks, sapero.  I will study it.  I did have one working.  If I can find time today I'll post it and you can tell me why I shouldn't do what I did.  :)
Clint
			
			
			
				This was the way I did it.  ;D
$INCLUDE "windowssdk.inc"
pointer ptxt
string txt
'find filename that was sent
ptxt=GetCommandLine()
txt=*<STRING>ptxt
IF INSTR(txt," "+chr$(34))>0
	txt=MID$(txt,INSTR(txt," "+chr$(34))+2)
	txt=LEFT$(txt,LEN(txt)-1)
ELSE
	txt=""
ENDIF
'create mutex and see if it already exists
INT Mutex=CreateMutex(NULL,TRUE,"TestForRightClick")
IF GetLastError()=ERROR_ALREADY_EXISTS
	'get window handle to program
	UINT wex=FindWindow(NULL,"Resize JPG")
	while wex=0
		wex=FindWindow(NULL,"Resize JPG")
	ENDWHILE
	'get list box handle
	UINT eex=FindWindowEx(wex,0,"LISTBOX",0)
	while eex=0
		eex=FindWindowEx(wex,0,"LISTBOX",0)
	ENDWHILE
	
	'add message to listbox
	SENDMESSAGE eex,LB_ADDSTRING,0,txt
	END
ENDIF
'this is the first instance of the window
window w1
OPENWINDOW w1,0,0,320,240,@TOPMOST|@TOOLWINDOW,0,"Resize JPG",&handler
CONTROL w1,@LISTBOX,"LISTBOX",10,10,295,195,@VSCROLL|@HSCROLL|@TABSTOP|@CTLISTSORT,1
SETHORIZEXTENT w1,1,500
SETFONT w1,"Times New Roman",8,400,0,1
'add filename to listbox
IF txt<>""
	ADDSTRING w1,1,txt
ENDIF
run=1
WAITUNTIL run=0
CloseHandle(Mutex)
CLOSEWINDOW w1
END
SUB handler()														'Windows handler
	SELECT @MESSAGE													'message that window sent
		case @IDCREATE												'new window created
			CENTERWINDOW w1											'center the window in the screen
		case @IDCLOSEWINDOW											'the 'X' was clicked,
			run=0													'set run to 0 so the program will end
	
	ENDSELECT
ENDSUB
Later,
Clint
			
			
			
				Quotetell me why I shouldn't do what I did
Ok :)
Bug: Lines 3-15.
a) It is a very bad idea to copy a string of length up to 32768 bytes, to a 255 bytes buffer (line 12).
   example command line: resize{.exe} "32000 characters here"
b) If the full path to your program will contain spaces (or the file name), string txt will include the .exe part. To skip .exe part, set ptxt to PathGetArgs(GetCommandLine()) or skip it manually. PathGetArgs defined in shlwapi.inc will return a pointer to the space after exe part (if any), or (will return) the pointer to terminating NULL character
c) What if you, or someone else executes your program with two parameters?
   resize path1 path2
Lines 22-24 and 28-30 - heating loops should be reserved for low speed microprocessors, or extreme calculations ;) A simple Sleep(1) inside such loop is welcome, and drastically increases the overall performance.
The two loops are very primitive - do something while it fails. If you run a second instance while the first instance is closing (window is closed and the mutex is still allocated), one of the loops will never terminate. This is one of possible solutions:
	UINT wex=FindWindow(NULL,"Resize JPG")
	while wex=0
		' sleep for ~1ms and check if the first instance closed
		if (WaitForSingleObject(Mutex, 1) <> WAIT_TIMEOUT) ' 0 or WAIT_ABANDONED
			' yup it closed! Now this thread is owning the mutex, so jump to OPENWINDOW (optionally).
		endif
		wex=FindWindow(NULL,"Resize JPG")
	ENDWHILEThe same for the second loop. WaitForSingleObject (while the first instance is running) is working here like Sleep. It will return zero after the first instance closes handle to its mutex, WAIT_ABANDONED if the thread owning the mutex terminates (second argument in CreateMutes is TRUE), WAIT_FAILED if the handle passed to WaitForSingleObject is invalid, or WAIT_TIMEOUT if the timeout interval elapses.
Additional failure counter inside loops will prevent your loops from infinite waiting:
	UINT wex=FindWindow(NULL,"Resize JPG")
	int failureCounter = 0
	while wex=0
		' sleep for ~100 ms and check if the first app closed
		if (WaitForSingleObject(Mutex, 100) <> WAIT_TIMEOUT) ' 0 or WAIT_ABANDONED
			' yup it closed! Now this thread is owning the mutex, so jump to OPENWINDOW (optionally).
		endif
		wex=FindWindow(NULL,"Resize JPG")
		failureCounter++
		if (failureCounter >= 20)
			' window is missing for two seconds, its thread must be hung
		endif
	ENDWHILENote the increased timeout in WaitForSingleObject - 100ms instead 1ms. When passing 1 as the timeout, the system may use a greater timeout: 10-20 ms, depending on your hardware (timeGetDevCaps api). 100ms is a safe compromise where +- 20ms makes not a big difference in our waiting loops.
			
 
			
			
				Thanks a bunch sapero.  I am printing that out and will study that too.
Later,
Clint
			
			
			
				This is the DDE version - it does not require command line parsing, because parameters are passed via shell, DDE messages.
By desing it uses single instance (note that there is no mutex), but multiple instances are possible with low level CreateProcess function.
DDE (at least on XP and below) is very sensitive for hung GUI programs.
The registry part installs additionally this program in the Open With shell menu.
$include "shlwapi.inc"
autodefine "OFF"
$define SHELL_VERB "# Temporary IWBasic Verb DDE"
$define FILE_EXT   ".jpg"
$define REGISTER_FOR_CURRENT_USER TRUE
' exe file name should be unique to prevent collisions in HKCR\Applications
enum controls
	IDC_FILES = 1000
	IDC_UNREGISTER
endenum
DIALOG d1
RegisterShellMenuVerb(SHELL_VERB, FILE_EXT, REGISTER_FOR_CURRENT_USER) ' install registry keys
CREATEDIALOG d1,0,0,369,221,0x80CA0080,0,"Single Instance DDE",&d1_handler
CONTROL d1,@LISTBOX,"",6,6,356,182,0x50A10140,IDC_FILES
CONTROL d1,@BUTTON,"Unregister shell menu verb",132,194,150,20,0x50000000,IDC_UNREGISTER
DOMODAL d1
end
SUB d1_handler(),int
	ddeHandleMessage(d1.hWnd, @MESSAGE, @WPARAM, @LPARAM)
	SELECT @MESSAGE
		CASE @IDINITDIALOG
			CENTERWINDOW d1
		CASE @IDCLOSEWINDOW
			CLOSEDIALOG d1,@IDOK
		CASE @IDCONTROL
			select @WPARAM
				' @WPARAM = @CONTROLID | (@NOTIFYCODE<<16);  is equal to  MAKELONG(@CONTROLID, @NOTIFYCODE)
				' @LPARAM - control handle
				case IDC_UNREGISTER ' @NOTIFYCODE is zero
					EnableWindow(@LPARAM, FALSE)
					UnRegisterShellMenuVerb(SHELL_VERB, FILE_EXT, REGISTER_FOR_CURRENT_USER)
			endselect
			'SELECT @CONTROLID
			'ENDSELECT
	ENDSELECT
	RETURN 0
ENDSUB
sub LoadFileA(LPSTR path)
	' ignore duplicates
	if (SendDlgItemMessageA(d1.hWnd, IDC_FILES, LB_FINDSTRINGEXACT, -1, path) = -1)
		SendDlgItemMessageA(d1.hWnd, IDC_FILES, LB_ADDSTRING, 0, path)
	endif
endsub
sub LoadFileW(LPWSTR path)
	' ignore duplicates
	if (SendDlgItemMessageW(d1.hWnd, IDC_FILES, LB_FINDSTRINGEXACT, -1, path) = -1)
		SendDlgItemMessageW(d1.hWnd, IDC_FILES, LB_ADDSTRING, 0, path)
	endif
endsub
' DDE main handler
sub ddeHandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	select (uMsg)
		case WM_DDE_INITIATE
			OnDdeInitialize(hwnd, wParam, lParam&0xFFFF, lParam>>16)
		case WM_DDE_EXECUTE
			OnDdeExecute(hwnd, wParam, lParam)
		case WM_DDE_TERMINATE
			PostMessage(wParam, WM_DDE_TERMINATE, hwnd, 0)
	endselect
endsub
' DDE helper
sub OnDdeInitialize(HWND hwnd, HWND hwndClient, ATOM atomApplication, ATOM atomTopic)
	istring szPath[MAX_PATH]
	GetModuleFileName(0, &szPath, MAX_PATH)
	LPTSTR pName = PathFindFileName(szPath)
	ATOM aApp = GlobalAddAtom(pName)
	ATOM aTop = GlobalAddAtom("system")
	if ((atomApplication = aApp) and (atomTopic = aTop))
		SendMessageA(hwndClient, WM_DDE_ACK, hwnd, MAKELPARAM(aApp, aTop))
	endif
	GlobalDeleteAtom(aApp)
	GlobalDeleteAtom(aTop)
endsub
' DDE helper
sub OnDdeExecute(HWND hwnd, HWND hwndClient, HGLOBAL hgl)
	istring szPath[MAX_PATH]
	iwstring szPathW[MAX_PATH]
	USHORT code = 0x0000
	if (hgl)
		LPTSTR text = GlobalLock(hgl)
		if (text)
			' [Open("%1")]
			if (!strnicmp(text, "[Open(\"", 7))
				LPTSTR path = &*<UCHAR>text[7]
				LPTSTR pend = strstr(path, "\")]")
				if (pend)
					int cch = pend - path
					if (cch < MAX_PATH)
						MoveMemory(&szPath, path, cch)
						szPath[cch] = 0
						BringMainWindowToTop(hwnd)
						LoadFileA(szPath)
						code = 0x8000
					endif
				endif
			elseif (!_wcsnicmp(text, L"[Open(\"", 7))
				LPWSTR pathW = *<WCHAR>text[7]
				LPWSTR endW = wcsstr(pathW, L"\")]")
				if (endW)
					cch = (endW - pathW)/2
					if (cch < MAX_PATH)
						MoveMemory(&szPathW, pathW, cch<<1)
						szPathW[cch] = 0
						BringMainWindowToTop(hwnd)
						LoadFileW(pathW)
						code = 0x8000
					endif
				endif
			endif
			GlobalUnlock(text)
		endif
	endif
	PostMessage(hwndClient, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, code, hgl))
endsub
' DDE helper
sub BringMainWindowToTop(HWND hwnd)
	if (IsIconic(hwnd)) then _ShowWindow(hwnd, SW_RESTORE)
	SetActiveWindow(hwnd)
	SetForegroundWindow(hwnd)
endsub
' Registry - installation
sub RegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
	istring szRegPath[256]
	istring commandline[MAX_PATH+8]
	istring szExeName[MAX_PATH]
	int size = 256
	HKEY hk
	DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
	if (SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, NULL, &szRegPath, &size))
		' szFileType (.jpg) is probably not yet registered in HKEY_CLASSES_ROOT.
		' If so, SHGetValue will return ERROR_PATH_NOT_FOUND error code.
		SHSetValue(hkRoot, "Software\\Classes\\jpg", NULL, REG_SZ, "jpgfile", 7)
		lstrcpy(&szRegPath, "jpgfile")
	endif
	' generate command line: "c:\program files\my stuff\jpg resizer.exe"
	int cchExeName
	RegistryRegUnRegHelper(&commandline, &szExeName, &cchExeName)
	PathQuoteSpaces(commandline)
	int cchVerb = len(szVerb)
	int cchCommandline = len(commandline)
	int cch1 = len(szRegPath) + cchVerb + 33
	int cch2 = cchExeName + cchVerb + 58
	int cch3 = len(szFileType) + cchExeName + 32
	pointer p = AllocHeap(__max(__max(cch1, cch2), cch3))
	' 33 = 1+len("Software\Classes\\shell\\command")
	' 58 = 1+len("Software\Classes\Applications\\shell\\ddeexec\application")
	' 32   1+len("Software\Classes\\OpenWithList\")
	if (p)
		' 1. shell verb:
		' Software\Classes\jpgfile\shell\open\command
		wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\command", szRegPath, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, commandline, cchCommandline)
		' with DDE
		' Software\Classes\jpgfile\shell\open\ddeexec
		wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\ddeexec", szRegPath, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, "[open(\"%1\")]", 12)
		' Software\Classes\jpgfile\shell\open\ddeexec\application
		wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\ddeexec\\application", szRegPath, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, szExeName, cchExeName)
		' Software\Classes\jpgfile\shell\open\ddeexec\topic
		wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\ddeexec\\topic", szRegPath, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, "system", 6)
		' 2. DDE keys for Open With
		' Software\Classes\Applications\jpgresizer.exe\shell\open\command
		wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\command", szExeName, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, commandline, cchCommandline)
		' Software\Classes\Applications\jpgresizer.exe\shell\open\ddeexec
		wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\ddeexec", szExeName, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, "[open(\"%1\")]", 12)
		' Software\Classes\Applications\jpgresizer.exe\shell\open\ddeexec\application
		wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\ddeexec\\application", szExeName, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, szExeName, cchExeName)
		' Software\Classes\Applications\jpgresizer.exe\shell\open\ddeexec\topic
		wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\ddeexec\\topic", szExeName, szVerb)
		SHSetValue(hkRoot, p, NULL, REG_SZ, "system", 6)
		' 3. keys for Open With
		' Software\Classes\jpgfile\OpenWithList\jpgresizer.exe
		wsprintf(p, "Software\\Classes\\%s\\OpenWithList\\%s", szFileType, szExeName)
		if (!RegCreateKeyEx(hkRoot, p, 0, 0, 0, KEY_WRITE, 0, &hk, 0))
			RegCloseKey(hk)
		endif
		FreeHeap(p)
	endif
endsub
' Registry - uninstallation
sub UnRegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
	istring szRegPath[256]
	istring commandline[MAX_PATH+8]
	istring szExeName[MAX_PATH]
	int size = 256
	DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
	if (!SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, NULL, &szRegPath, &size))
		int cchExeName
		RegistryRegUnRegHelper(&commandline, &szExeName, &cchExeName)
		int cch1 = len(szRegPath) + len(szVerb) + 25 ' 25 = 1+len("Software\Classes\\shell\")
		int cch2 = cchExeName + 31                   ' 31 = 1+len("Software\Classes\Applications\")
		int cch3 = len(szFileType) + cchExeName + 32 ' 32 = 1+len("Software\Classes\\OpenWithList\")
		pointer p = AllocHeap(__max(__max(cch1, cch2), cch3))
		if (p)
			wsprintf(p, "Software\\Classes\\%s\\shell\\%s", szRegPath, szVerb)
			SHDeleteKey(hkRoot, p)
			wsprintf(p, "Software\\Classes\\Applications\\%s", szExeName)
			SHDeleteKey(hkRoot, p)
			wsprintf(p, "Software\\Classes\\%s\\OpenWithList\\%s", szFileType, szExeName)
			SHDeleteKey(hkRoot, p)
			FreeHeap(p)
		endif
	endif
endsub
sub RegistryRegUnRegHelper(LPSTR commandline, LPSTR pszExeName, pointer ppCchExeName)
	GetModuleFileName(0, commandline, MAX_PATH)
	lstrcpy(pszExeName, PathFindFileName(commandline))
	*<int>ppCchExeName = len(*<string>pszExeName)
endsub
			
			
			
				Thanks again Sapero.  I have a lot to learn.
Later,
Clint