Context Menu Script

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
ResonantStep
Posts: 4
Joined: 27 Apr 2019 06:32

Context Menu Script

#1 Post by ResonantStep » 27 Apr 2019 13:01

Before the details, I expose the context:
I wanted to have "copyto" and "moveto" in my extended context menu (shift + right click), together with copy as path. Problem: you can either hide (delete the key) or they always show.
I tried editing registry, adding values in shellex and/or clsid keys without success, so I decided to try "mimic" these options with a script, also for the challenge as I recently started as a hobby.

Using "sendto" context menu it was (relatively) easy, but still, while "move to" was easier, I struggled with the "copy to" function:
I had to move selected items to a temp folder first before being able to copy them. Here are the codes:

Move To folder...

Code: Select all

@if (@CodeSection == @Batch) @then


@echo off
setlocal
set cnt=0
set SelectionName='%~nx1'
cd /d "%~dp1"
	for %%A in (%*) do (
		set /a cnt+=1
	)

	if %cnt% GTR 1 set "SelectionName=these %cnt% items"

	for /F "delims=" %%a in ('CScript //nologo //E:JScript "%~F0" "Select the place where you want to move %SelectionName%, then click OK."') do (
	   set "TargetFolder=%%a"
	)
	
	for %%A in (%*) do (
		move /y %%A  "%TargetFolder%"
	) >NUL 2>&1
goto :eof


@end


var shl = new ActiveXObject("Shell.Application");
var folder = shl.BrowseForFolder(0, WScript.Arguments(0), 0x00000050,17);
WScript.Stdout.WriteLine(folder ? folder.self.path : "");
Copy To folder...

Code: Select all

@if (@CodeSection == @Batch) @then


"C:\Program Files\System Tools\System Utilities\NirCmd\nircmd.exe" win hide ititle "cmd.exe"
@echo off
setlocal
set "cnt=0"
set "SelectionName='%~nx1'"
set "StartFolder=%~dp1"


	cd /d "%StartFolder%"
	for %%A in (%*) do (
		set /a cnt+=1
	)
	if %cnt% GTR 1 set "SelectionName=these %cnt% items"

	for /F "delims=" %%B in ('CScript //nologo //E:JScript "%~F0" "Select the place where you want to copy %SelectionName%, then click OK."') do (
	   set "TargetFolder=%%B"
	)

	if "%TargetFolder%" == "%StartFolder%" goto :clean
	set "TmpTargetFolder=%TargetFolder%\TMP"
	if not exist "%TmpTargetFolder%" md "%TmpTargetFolder%"
	cd /d "%StartFolder%"
	for %%A in (%*) do (
	move /y %%A  "%TmpTargetFolder%" >NUL 2>&1
	)
	robocopy /e "%TmpTargetFolder%" "%TargetFolder%" >NUL 2>&1
	robocopy /MOVE /e "%TmpTargetFolder%" "%~dp1\" >NUL 2>&1
:clean
	if exist "%TmpTargetFolder%" rmdir "%TmpTargetFolder%" /s /q >NUL 2>&1
goto :eof


@end


var shl = new ActiveXObject("Shell.Application");
var folder = shl.BrowseForFolder(0, WScript.Arguments(0), 0x00000050,17);
WScript.Stdout.WriteLine(folder ? folder.self.path : "");
As you see I couldn't get the right "direct" commands for a "selected file and folder copy", I was either getting:
"xcopy cannot perform a cyclic copy", for ex. when "source" and "targetfolder" parent directory is the same, as happens when creating new folder next to selextion...
or ending up copying the whole source directory (together with items that were not selected at all),
or copying files and folders but without file structure,
copy, xcopy, robocopy...many tries.

So I finally decided to: first move selected files to temp directory (since move command works flawlessly), and then copy (twice) from there, as "full directory copy" seemed easier.
After getting things like "can not access TEMP/subdirectories" (send to shortcuts as admin doesn't work it seems), what worked was to make that TMP folder in Targetfolder.
It works, but not that fancy. So the 1st. question (the easiest) is:
Could someone kindly help with the syntax for a selected files+folders "direct copy", like a one liner instead of my ugly move+double robocopy :) )

2nd question (a grouped one, not most important) about the Folder Browser box:
I couldn't find a way to open the browser in "selected item(s)" folder (like real "copy to" does)
I know I can set a "root directory" (4th arg), but then you are not able to navigate above that directory (UGHH), that's why I put "17" which is "This PC".
Starting browser at selected item location works with "FolderBrowserDialog" function (another hybrid function), but then I can not have a text box (=value 10 in 3rd arg, here value 50=10+40),
and on my system it was much slower before opening the box.
In an idealistic world...For a perfect clone I would also like to customize the OK button and the browser window title (not the description)...but almost sure it's not possible that way.
If anybody have knowledge in boxes and folder selection, I'd like to learn anyway!

Now the best part :)
What if I want to have these functions in context menu (but extended context menu hehe).
I quickly remembered that from context menu all files are processed separately, meaning it opens one process for each item selected.
Googling around I found this neat little app: https://github.com/zenden2k/context-menu-launcher called singleinstance.exe
and which allow to pass various selected files to a single instance as variables.
cons: there is no option to hide the console window.Ok, I got around this changing top of the script like this (using nircmd), just getting a small flash.

Code: Select all

@if (@CodeSection == @Batch) @then


"C:\Program Files\System Tools\System Utilities\NirCmd\nircmd.exe" win hide ititle "cmd.exe"
@echo off
But even with a 5 seconds timeout and "MultiSelectModel"="Player" values in registry, it is limited to approx. 80 files before it starts to behave erratically.

So I decided to implement a counter, and "dummy folder" (create/check&exit).
Here it is:

Code: Select all

@if (@CodeSection == @Batch) @then


@echo off

if exist "Copy2FolderTMP" rmdir "Copy2FolderTMP" /s /q >NUL 2>&1
setlocal

set "cnt=0"
set "cnt2=0"
set "cnt3=0"
set "number=0"
set "StartFolder=%~dp1"
set "ItemFullPath=%~dpnx1"
set "ItemName=%~nx1"
set "SelectionName='%~nx1'"
set "ParsingDir=%TEMP%\Copy2FolderTMP"
set "PathSingleDir=%ParsingDir%\Items"
set "PathSingle=%PathSingleDir%\%~n1.txt"
set "PathListDir=%ParsingDir%\List"
set "PathList=%PathListDir%\DirList.txt"
set "FinalPathList=%PathListDir%\FinalDirList.txt"
set "Dummy=%ParsingDir%\Dummy"
if not exist "%ParsingDir%" md "%ParsingDir%" >NUL 2>&1
if not exist "%PathSingleDir%" md "%PathSingleDir%" >NUL 2>&1
if not exist "%PathListDir%" md "%PathListDir%" >NUL 2>&1

	cd /d "%PathSingleDir%"
	echo "%ItemFullPath%"> "%PathSingle%"
	for %%a in (*) do set /a "cnt+=1"
	cd /d "%PathListDir%"
:loop
	echo "%ItemFullPath%">> "%PathList%"
	for /f "delims=:" %%A in ('findstr /N /c:"%ItemFullPath%" "DirList.txt"') do set "number=%%A"

	if %number% GTR 1 (
		if not exist %Dummy% md %Dummy%
		endlocal & goto :eof
	)
	if %number% EQU 0 ( goto :loop)

	pathping 127.0.0.1 -n -q 1 -p 300 >nul
	if not exist "%Dummy%" (
		set "%MultiSelect%=OFF"
		goto :FolderBrowserBox
	)

:PathList
	cd /d "%PathSingleDir%"
	copy /b "*.txt" "%FinalPathList%" >NUL 2>&1
	for %%a in (*) do set /a "cnt+=1"
	
	cd /d "%PathListDir%"
	for /f %%a in (FinalDirList.txt) do set /a "cnt2+=1"

	if not %cnt3% EQU %cnt2% (
		set "cnt3=%cnt2%"
		set /a "cnt=0"
		set /a "cnt2=0"
		pathping 127.0.0.1 -n -q 1 -p 500 >nul
		goto :PathList
	)
	
	if %cnt2% EQU %cnt% (
		set "SelectionName=these %cnt% items"
		goto :FolderBrowserBox
	)
	pathping 127.0.0.1 -n -q 1 -p 500 >nul
	goto :PathList

:FolderBrowserBox
	cd /d "%StartFolder%"
	for /F "delims=" %%B in ('CScript //nologo //E:JScript "%~F0" "Select the place where you want to copy %SelectionName%, then click OK."') do set "TargetFolder=%%B"
	
	if "%TargetFolder%" == "" goto :Cleaning
	if "%TargetFolder%" == "%StartFolder%" goto :Cleaning
	set "TmpTargetFolder=%TargetFolder%\TMP"
	if not exist "%TmpTargetFolder%" md "%TmpTargetFolder%" >NUL 2>&1

	cd /d "%PathListDir%"
	if "%MultiSelect%" == "OFF" (
	  move "%ItemFullPath%" "%TmpTargetFolder%" >NUL 2>&1
	) else (
		for /F "tokens=*" %%A in (FinalDirList.txt) do (
			move /y %%A  "%TmpTargetFolder%" >NUL 2>&1
		)
	)
	robocopy /e "%TmpTargetFolder%" "%TargetFolder%" >NUL 2>&1
	robocopy /MOVE /e "%TmpTargetFolder%" "%~dp1\" >NUL 2>&1

:Cleaning
	TIMEOUT /T 1 /nobreak >NUL 2>&1
::Go to TEMP as a safety measure before delete task
	cd /d "%TEMP%"
	if exist "%TmpTargetFolder%" (
		cd /d "%TEMP%\Copy2FolderTMP"
		for /f "delims=" %%i in ('dir /b') do (rmdir "%%i" /s /q || del "%%i" /f /s /q) >NUL 2>&1
	)
	cd /d "%TEMP%"
	rmdir "Copy2FolderTMP" /s /q >NUL 2>&1
endlocal & goto :eof


@end


var shl = new ActiveXObject("Shell.Application");
var folder = shl.BrowseForFolder(0, WScript.Arguments(0), 0x00000050,17);
WScript.Stdout.WriteLine(folder ? folder.self.path : "");
It works...But since it creates one file for each item selected it is slow and resources hogging, and finally I am still limited to the "MultipleInvokePromptMinimum" value
under "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer" key.
200 items for me, I believe 15 as default.

So the BIG question are:
Is there a better/easier way I could not think of for that copy to (and move to) function ? Like invoke CLSID/copy to service, a bit similar to copy as path (useless) tweak.
and/or ideally how can I pass a wanted number of selected files (and/or folders) to one instance/batch in a reliable/faster way, using a bat file (I know just a bit of powershell and very few vbs)

Otherwise also some "code optimization" hints, always keen to learn!


ps: for the send to menu shortcut creation, as I struggled with the syntax too I put it here as it may help others.
Note 2 under this link was very helful to find what arguments and how to enter them.
https://www.computerperformance.co.uk/p ... -shortcut/

Code: Select all

@echo off
	PowerShell -NoProfile -ExecutionPolicy Bypass "$s=(New-Object -COM WScript.Shell).CreateShortcut('%AppData%\Microsoft\Windows\SendTo\Copy To folder....lnk');$s.TargetPath='%scriptpath%\CopyToFolder.bat';$s.WorkingDirectory='%AppData%\Microsoft\Windows\SendTo';$s.WindowStyle=7;$s.Description='Copy selection to choosen folder';$s.IconLocation='%SystemRoot%\System32\SHELL32.dll,4';$s.Save()" >NUL 2>&1
	PowerShell -NoProfile -ExecutionPolicy Bypass "$s=(New-Object -COM WScript.Shell).CreateShortcut('%AppData%\Microsoft\Windows\SendTo\Move To folder....Lnk');$s.TargetPath='%scriptpath%\MoveToFolder.bat';$s.WorkingDirectory='%AppData%\Microsoft\Windows\SendTo';$s.WindowStyle=7;$s.Description='Move selection to to choosen folder';$s.IconLocation='%SystemRoot%\System32\SHELL32.dll,4';$s.Save()" >NUL 2>&1

goto :eof
That's it...thanks in advance, sorry if it's too long or confuse (that's my first post here.)

Post Reply