13

I'd like to create a Windows Explorer context menu action for multiple selected files.

I created a key HKEY_CLASSES_ROOT\*\shell\MyAction\command with a string value

C:\python37\python.exe "c:\test\test.py" "%1"

This script only displays the command-line arguments for now (for debugging purpose): import sys; print(sys.argv); input()

When I select two files a.txt, b.txt in the Explorer, and right click on "MyAction", then:

  • I'd like to have this script called once, and the command-line arguments passed to Python should be

    ["c:\test\test.py", "c:\a.txt", "c:\b.txt"]
    
  • instead I get this script called twice (once independently for each file), and the command-line arguments passed are:

    ["c:\test\test.py", "c:\a.txt"]
    ["c:\test\test.py", "c:\b.txt"]
    

How to make a context menu action call the command only once when 2 files are selected, with the 2 files as command-line arguments?


TL;DR I would like this to be launched:

C:\python37\python.exe "c:\test\test.py" "a.txt" "b.raw" "file_with_noext"

and not:

C:\python37\python.exe "c:\test\test.py" "a.txt"
C:\python37\python.exe "c:\test\test.py" "b.raw"
C:\python37\python.exe "c:\test\test.py" "file_with_noext"

when using the context menu action on multiple files.


Note: Open With on multiple files? and its answers don't solve it; I tried with HKEY_CLASSES_ROOT\SystemFileAssociations\*\shell\MyAction\command but this menu item doesn't appear when I select multiple files (let's say a.txt, b.raw, file_with_noext, and testdir\).

Basj
  • 2,143

5 Answers5

5

I created a small program that completely solves this problem. https://github.com/ge9/ExecuteCommand-Pipe

It is based on this Microsoft sample code: https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/winui/shell/appshellintegration/ExecuteCommandVerb

It uses COM (Component Object Model) technology of Windows, so there are no restriction on the number or path length of passed files. Also it doesn't involve any inter-process communication, preserving the order of files. The command for opening files is specified in registry values. See the repository for details.

turgenev
  • 126
3

Apologies if you got the answer already but I think the only way to do this is to place a shortcut to your app in the SendTo folder (You can open it from a Run window by typing shell:sendto) and then highlighting both files and choosing your program from the "Send to" context menu.

1

I've met a similar issue but in rather different context... So here is my solution. In fact, I use the same registry hack that leads to multiple process invokations, but the receiving command is a powershell script that is aware of the possibility of being run multiple times, so it checks the list of current processes run with the same command (and preassumably different arguments), than checks whether its PID is maximal and continues the execution afterwards, else terminates itself. The last script survived has the list of arguments of all sibling processes with which they were started, so you can in synchroneous way do with this list whatever you want, for example, join them and pass as argument to python.

Here's .reg file example:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\SystemFileAssociations*\shell\exmaple] @="&Merge selected files..."

[HKEY_CLASSES_ROOT\SystemFileAssociations*\shell\example\command] @="powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\path\to\example.ps1" "%1""

I wrapped main logic into a function that receives script name (couldn't access it directly from within function) and return space separated list of filenames. Example usage:

# Example call to the function and usage of its result
$scriptName = $MyInvocation.MyCommand.Name
$arguments = Get-ScriptArguments -scriptName $scriptName
Write-Host "Returned arguments: $arguments"

python script.py $arguments

Wait for user input before closing

Read-Host "Press Enter to exit"

The function:

function Get-ScriptArguments {
    param (
        [string]$scriptName
    )
# Get the current PowerShell process ID
$currentProcessId = $PID

# Get all PowerShell processes
$processes = Get-Process -Name "powershell"

# Initialize counter for instances and an array for arguments
$instanceCount = 0
$argumentsList = @()
$scriptProcesses = @()

foreach ($process in $processes) {
    try {
        # Get command line arguments for the process
        $commandLine = (Get-CimInstance Win32_Process -Filter "ProcessId = $($process.Id)").CommandLine

        # Check if the command line includes the current script name
        if ($commandLine -like "*$scriptName*") {
            $instanceCount++
            $scriptProcesses += $process

            # Extract the quoted path from the command line (searching from the right)
            if ($commandLine -match '.*(".*")$') {
                $arguments = $matches[1]
                $argumentsList += $arguments
            }

            # Output the process ID and command line
            Write-Host "Process ID: $($process.Id), Command Line: $commandLine"
        }
    } catch {
        # Handle any exceptions (e.g., access denied) and output the error
        Write-Host "Error processing process ID $($process.Id): $($_.Exception.Message)"
    }
}

# Check if the current script's PID is the highest among instances of this script
$maxPid = ($scriptProcesses | Select-Object -ExpandProperty Id | Measure-Object -Maximum).Maximum
if ($currentProcessId -ne $maxPid) {
    Write-Host "Current script PID ($currentProcessId) is not the highest. Terminating script."
    exit
} else {
    Write-Host "Current script PID ($currentProcessId) is the highest. Continuing execution."
    return $argumentsList -join " "
}

}

0

SUMMARY

  1. In Registry put the target program as Context Menu Create File.exe which writes reg.txt file.

  2. In your main program loop every 1 second to check if reg.txt exists. If it exists, kill the Context Menu Create File.exe and delete the reg.txt file. Then copy the selected file paths and manipulate with them.

  3. If your program loops to check for reg.txt, then you need to start the program before executing context menu either on startup or manually.

I did this with AutoHotkey.

These 2 AutoHotkey scripts below allow you to add a Open With Chrome context menu item in Windows Explorer to open multiple selected files.

You can leave all variable values as they are, but if you want to change the contextMenu value and program name then see 3.1.

INSTRUCTIONS:

Create 2 files in the same directory:

  1. Create 1st program Add To Context Menu And Create Startup Shortcut.ahk

    1. RunAsAdmin Label ensures that the script runs as admin (fixes adding Registry values).

      1. The If (!A_IsAdmin) checks if current user is NOT admin, A_IsAdmin is a built in AutoHotkey variable that returns 1 if user is admin, 0 otherwise.
      2. Run, \*RunAs "%A_ScriptFullPath%" *RunAs parameter runs the script as admin, "%A_ScriptFullPath%" gets the full path of the current executing script.
      3. ExitApp exits the current script instance running without admin privileges.
      4. Because the Run command runs the script again with admin privileges it will skip the IF condition and continue executing code below.
    2. ContextMenuCreateFile: Label creates a Context Menu Create File.exe which creates a file reg.txt and exits Context Menu Create File.exe after it has written the file reg.txt. Make sure you specify where your Ahk2Exe.exe path is in the RunWait command.

    3. Add To Context Menu: Label adds the Registry entry which runs Context Menu Create File.exe.

      1. Set the contextMenu variable to what needs to be displayed in the Context Menu. (The program name is set to contextMenu)
      2. Set the regPath to your desired Registry path.
      3. When it executes the MsgBox, check if the command is added to the Registry in the address bar.
    4. CreateStartupShortcut: Label creates the shortcut of the main program Open With Chrome.exe in Startup folder.

Add To Context Menu And Create Startup Shortcut.ahk

; =============Recommended Settings=============
#NoEnv
SetWorkingDir %A_ScriptDir%
#Warn
CoordMode, Mouse, Window
SendMode Input
#SingleInstance Force
SetTitleMatchMode 2
SetTitleMatchMode Fast
DetectHiddenWindows Off
DetectHiddenText On
#WinActivateForce
#NoTrayIcon
SetControlDelay 1
SetWinDelay 0
SetKeyDelay -1
SetMouseDelay -1
SetBatchLines -1
#Persistent
#MaxThreadsPerHotkey 2
; =============Recommended Settings=============

AddToContextMenuAndCreateStartupShortcut: RunAsAdmin: ; =============RunAsAdmin============= If (!A_IsAdmin) ; IF NOT Admin { Run, *RunAs "%A_ScriptFullPath%" ; Run script as admin ExitApp ; Exit the current instance running without admin privileges } ContextMenuCreateFile: ; =============ContextMenuCreateFile============= contextMenuCreateFileAhk := (LTrim "#NoEnv #NoTrayIcon #SingleInstance Force SetWorkingDir %A_ScriptDir% SetBatchLines -1

ContextMenuCreateFile: FileDelete, reg.txt FileAppend, , reg.txt ExitApp Return" ) ; contextMenuCreateFileAhk FileDelete, Context Menu Create File.exe ; DEL Context Menu Create File.exe FileDelete, Context Menu Create File.ahk ; DEL Context Menu Create File.ahk FileAppend, %contextMenuCreateFileAhk%, Context Menu Create File.ahk ; MAKE Context Menu Create File.ahk RunWait, C:\Program Files\AutoHotkey\Compiler\Ahk2Exe.exe /in "Context Menu Create File.ahk" /out "Context Menu Create File.exe" ; Convert AHK to EXE FileDelete, Context Menu Create File.ahk ; DEL Context Menu Create File.ahk AddToContextMenu: ; =============AddToContextMenu============= path := "" ; path program := "Context Menu Create File" ; program contextMenu := "Open With Chrome" ; contextMenu regPath := "HKCR*\shell" ; regPath StringReplace, regKey, contextMenu, %A_Space%, , A ; regKey regKey := 0 regKey ; regKey Loop, Files, %program%.exe, F ; Find Program.exe In Current Dir { path := A_LoopFileLongPath ; Set Program Path } cmd := (LTrim "reg add """ regPath "" regKey """ /ve /t REG_SZ /d """ contextMenu """ /f reg add """ regPath "" regKey "\command"" /ve /t REG_SZ /d """"" path """`"" /f" ) ; Registry FileDelete, Add To Context Menu.bat ; CREATE Add To Context Menu.bat FileAppend, %cmd%, Add To Context Menu.bat ; CREATE Add To Context Menu.bat RunWait, Add To Context Menu.bat, , Hide ; RUN Add To Context Menu.bat (*RunAs ADMIN) FileDelete, Add To Context Menu.bat ; DEL Add To Context Menu.bat Run, regedit ; regedit WinWait, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe ; Registry Sleep, 333 ControlSetText, Edit1, %regPath%%regKey%\command, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe ; regPath ControlFocus, Edit1, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe ; regPath ControlSend, Edit1, {Enter}, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe ; regPath ControlSend, SysListView321, {Control Down}{NumpadAdd}{Control Up}, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe ; regPath ControlSend, SysListView321, {F5}, Registry Editor ahk_class RegEdit_RegEdit ahk_exe regedit.exe ; regPath MsgBox, 262192, CHECK, Check If Added %contextMenu% To Registry ; CHECK CreateStartupShortcut: ; =============CreateStartupShortcut============= path := "" ; path program := contextMenu ; program Loop, Files, %program%.exe, F ; Find Program.exe In Current Dir { path := A_LoopFileLongPath ; Set Program Path } FileCreateShortcut, %path%, %A_Startup%%program%.lnk ; Create Startup Shortcut Run, %A_Startup%, , Max ; Check If Shortcut Created Run, "%program%.exe" ; Run Program MsgBox, 262144, CHECK, Check If Shortcut Created ; CHECK ExitApp ; ExitApp Return

  1. Create 2nd program Open With Chrome.ahk which is the main program.
    1. Here a Loop is created and checks every 1 second if reg.txt exists.
    2. IfExist, reg.txt it kills the Context Menu Create File.exe and deletes the reg.txt.
    3. Then it activates explorer.exe window and copies all selected file paths to CLIPBOARD.
    4. If CLIPBOARD contains .,\ to make sure CLIPBOARD contains path "\" with extension ".".
    5. The list of selected files is saved in selectedFiles variable.
    6. The Loop below chromeParams := "" loops through selected files, gets the filePaths and surrounds them with double quotes, and StringReplace replaces the Windows path as url file path ex: C:\path\file.jpg to file:///path/file.jpg.
    7. Then the filePath is concatenated to chromeParams.
    8. StringTrimRight removes the last space from chromeParams string.
    9. Then Run, chrome.exe %chromeParams% is executed with %chromeParams% (list of selected files). (If the command doesn't open Chrome then put full path to Chrome, ex: Run, C:\Program Files (x86)\Google\Chrome\Application\chrome.exe with the same parameters)

Open With Chrome.ahk

; =============Recommended Settings=============
#NoEnv
SetWorkingDir %A_ScriptDir%
#Warn
CoordMode, Mouse, Window
SendMode Input
#SingleInstance Force
SetTitleMatchMode 2
SetTitleMatchMode Fast
DetectHiddenWindows Off
DetectHiddenText On
#WinActivateForce
#NoTrayIcon
SetControlDelay 1
SetWinDelay 0
SetKeyDelay -1
SetMouseDelay -1
SetBatchLines -1
#Persistent
#MaxThreadsPerHotkey 2
; =============Recommended Settings=============

OpenWithChrome: Loop ; Loop Start { Sleep, 1000 ; Fix High CPU IfExist, reg.txt ; IF reg.txt EXIST { RunWait, cmd /c taskkill /im "Context Menu Create File.exe" /f, , Hide ; Fix Opening 2 Compose Windows FileDelete, reg.txt ; DEL reg.txt WinActivate, ahk_class CabinetWClass ahk_exe explorer.exe ; Explorer CLIPBOARD := "" ; Clear Clipboard Send, {Control Down}{c}{Control Up} ; Copy File Paths ClipWait, 0 ; Clip Wait If CLIPBOARD contains .,\ ; IF CLIPBOARD contains .,
{ selectedFiles := CLIPBOARD ; selectedFiles chromeParams := "" ; chromeParams Loop, Parse, selectedFiles, n,r ; Loop Start selectedFiles { filePath := """file:///" A_LoopField """" ; filePath StringReplace, filePath, filePath, , /, A ; Replace \ with / chromeParams .= filePath . " " ; chromeParams .= %filePath%, } StringTrimRight, chromeParams, chromeParams, 1 ; Remove Last Space Run, chrome.exe %chromeParams% ; Open Files In Chrome } } } Return

  1. Convert both Add To Context Menu And Create Startup Shortcut.ahk and Open With Chrome.ahk to EXE files in the same directory using Ahk2Exe.exe --> (find in Start Menu, just browse file and hit convert)

  2. Execute the Add To Context Menu And Create Startup Shortcut.exe

  3. Select files, right click and the Open With Chrome context menu item should appear.

0

I have found a solution which works quite good:

Just use the context menu "sendTo" folder for this case

  1. create a script which tooks the input file paths as arguments (here i use a ps1 script to compare multiple script files in VSCode)

  2. create a shortcut pointing to this script. When prompted for the location of the item, enter e.g. the following command: powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\scripts\diff_files_in_vscode.ps1"

  3. move the shortcut into the sendTo directory. You can open it with win + R and the typing in: shell:sendto. Just drag and drop you shortcut here.

Finally the explorer will send all marked files together onto you script as input arguments

Manute
  • 1