orbs Posted October 19, 2023 Share Posted October 19, 2023 Accept Multiple Files UDF is inspired by this topic and this topic. Introduction if your app (compiled script) is launched from Windows Explorer context menu, then you've probably noticed that when multiple files are selected, only one file is passed to your app, so Windows starts multiple instances of your app, one for each file selected. few simple ways to overcome this issue are: 1) put a shortcut to your app on your "Send To..." context menu. this passes all the selected files as individual parameters to a single instance of your app. 2) put a shortcut to your app somewhere accessible, and drag the selected files to that shortcut. that will give you the same effect. (an accessible location might not include the task bar, unfortunately. but the Start menu for example is ok). 3) use an intermediate launcher, like this one (not tested by me). if none of these apply to you, then this UDF allows your app to handle this issue internally. Usage - yes, it is a one-liner: Global $aFiles = _AcceptMultipleFiles() Example Script - with comprehensive comments inside: #AutoIt3Wrapper_Au3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7 #include <Array.au3> ; this is needed only For the _ArrayDisplay later In this example script. #include 'AMF.au3' ; include the "Accept Multiple Files" UDF. #NoTrayIcon ; disable this interactive feature. Global $hTimer = TimerInit() ; for testing purposes Global $aFiles = _AcceptMultipleFiles() Global $fDiff = Round(TimerDiff($hTimer)) / 1000 ; determine how long the process took #cs calling _AcceptMultipleFiles makes the calling script accept all files passed as parameters to multiple instances of the calling script, typically invoked by Windows Explorer context menu. list of files is returned as an array. this is best performed as early as possible in your script, and definitely before any user interaction. it will either make this instance the 'master', that will accept multiple files to process (and continue running), or submit its file to the master instance if one is already active (and exit). *NOTE: if there are files to process, and if this is NOT the 'master' instance, then this line will never be reached. *NOTE: if only one file is selected - thus only one instance is invoked - then that instance becomes the 'master' and the files array contains only the one file. if your code made it this far, there are two possible scenarios: 1) there are files to process, and they are stored in the array. element zero and @extended both store the number of files. 2) your app was called with no parameters, or too many, or one that is not a file. in this case element zero = 0 and @extended = 0. hence, you can use element zero to decide on your next action. something like this: #ce If $aFiles[0] > 0 Then ; <<<<<<<<<< do your stuff with the files listed in $aFiles >>>>>>>>>> _ArrayDisplay($aFiles, 'AMF Example (' & $fDiff & ' sec)') Else ; <<<<<<<<<< do your stuff when no parameters were specified, or too many parameters, or a single parameter which is not a valid file >>>>>>>>>> MsgBox(0, 'AMF Example (' & $fDiff & ' sec)', 'This was the command line:' & @CRLF & $CmdLineRaw) EndIf UDF: expandcollapse popup#include-Once ; #INDEX# ======================================================================================================================= ; Title .........: Accept Multiple Files ; AutoIt Version : 3.3.16.1 ; UDF Version ...: 2.0 ; Status ........: Production ; Language ......: English ; Description ...: Allows a script to accept multiple files from Windows Explorer context menu, which invokes multiple instances. ; One instance becomes the 'master'. All other instanes submit their files to the master and exit, so only the ; 'master' instance remains active and can process all files. ; Author(s) .....: orbs ; =============================================================================================================================== ; #VARIABLES# =================================================================================================================== Global $__g_AMF_bDebug = False ; =============================================================================================================================== ; #CURRENT# ===================================================================================================================== ; _AcceptMultipleFiles ; =============================================================================================================================== ; #FUNCTION# ==================================================================================================================== ; Name ..........: _AcceptMultipleFiles ; Description ...: Accepts multiple files given as parameters to multiple instances of the calling script. ; Syntax ........: _AcceptMultipleFiles([$iAttempts = 10[, $iInterval = 10[, $bDebug = False]]]) ; Parameters ....: $iAttempts - [optional] number of attempts of searching for submitted files. Default (and minimum) is 10. ; $iInterval - [optional] number of milliseconds between attempts. Default (and minimum) is 10. ; $bDebug - [optional] default is False. if True, log files are created in the temporary folder. ; Return values .: a 1-based array of all files submitted by all instances. @extended is set to file count. ; Author ........: orbs ; Modified ......: ; Remarks .......: - if no parameters were specified, or too many parameters, or a single parameter which is not a valid file, ; then the function returns an array with element [0]=0, and @extended is set to 0. ; - to improve reliability, the calling script may increase $iAttempts and/or $iInterval. ; Related .......: ; Link ..........: ; Example .......: Yes ; =============================================================================================================================== Func _AcceptMultipleFiles($iAttempts = 10, $iInterval = 10, $bDebug = False) ; process parameters If ($iAttempts = Default) Or ($iAttempts < 10) Then $iAttempts = 10 If ($iInterval = Default) Or ($iInterval < 10) Then $iInterval = 10 If $bDebug = Default Then $bDebug = False $__g_AMF_bDebug = $bDebug __AMF_Log('enter _AcceptMultipleFiles') __AMF_Log('attempts: ' & $iAttempts) __AMF_Log('interval: ' & $iInterval) ; initialize return data Local $aFiles[1] = [0] ; validate parameter If Not ($CmdLine[0] = 1 And FileExists($CmdLine[1])) Then Return SetExtended(__AMF_Log('invalid param => return empty array'), $aFiles) ; prepare variables and parent (root) folder Local Const $sAMF_Root = @LocalAppDataDir & '\AMF' __AMF_Log('AMF root: ' & $sAMF_Root) __AMF_Log('root DirCreate = ' & DirCreate($sAMF_Root)) Local Const $sAMF_Path = @LocalAppDataDir & '\AMF\' & __AMF_CreateGUID(StringRight(@ScriptFullPath, 16)) Local Const $sAMF_MasterFile = $sAMF_Path & '\master' __AMF_Log('AMF path: ' & $sAMF_Path) __AMF_Log('master file: ' & $sAMF_MasterFile) Local $iIteration = 0 Local $bFound Local $hSearch Local $sAMF_File Local $sFile ; determine if master While Not __AMF_TakeOver($sAMF_Path, $sAMF_MasterFile) ; see if this should become the master process. If __AMF_MasterIsActive($sAMF_MasterFile, $iAttempts, $iInterval) Then ;; if not, then check if there is already an active master process. __AMF_SubmitFile($sAMF_Path, $CmdLine[1]) ;;; if so, then submit the file to the master and exit. __AMF_Log('file submitted => exit') Exit Else __AMF_Cleanup($sAMF_Path) ;; if there is no active master, assume the past master has crashed. cleanup and repeat trying to take over. EndIf WEnd __AMF_Log('this instance is master') ; start with first file, submitted by the master ReDim $aFiles[UBound($aFiles) + 1] $aFiles[0] += 1 $aFiles[UBound($aFiles) - 1] = $CmdLine[1] ; process submitted files __AMF_Log('searching for submitted files') Do $iIteration += 1 $bFound = False __AMF_Log('start iteration #' & $iIteration) For $i = 1 To $iAttempts __AMF_Log('attempt #' & $i) FileClose($hSearch) $hSearch = FileFindFirstFile($sAMF_Path & '\PID_*') If @error Then __AMF_Log('FileFindFirstFile @error=' & @error & ' => next attempt') FileClose($hSearch) Sleep($iInterval) ContinueLoop EndIf $sAMF_File = FileFindNextFile($hSearch) If @error Then __AMF_Log('FileFindNextFile @error=' & @error & ' => next attempt') Sleep($iInterval) ContinueLoop EndIf $bFound = True __AMF_Log('$sAMF_File = ' & $sAMF_File) $sAMF_File = $sAMF_Path & '\' & $sAMF_File __AMF_Log('prefix full path = ' & $sAMF_File) __AMF_Log('FileGetAttrib = ' & FileGetAttrib($sAMF_File)) If StringInStr(FileGetAttrib($sAMF_File), 'R') Then __AMF_Log('Attrib R found') $sFile = FileReadLine($sAMF_File) __AMF_Log('FileReadLine = ' & $sFile) If $sFile = '' Then __AMF_Log('result is empty => repeat attempt') Sleep($iInterval) $i -= 1 ContinueLoop Else __AMF_Log('FileSetAttrib -R = ' & FileSetAttrib($sAMF_File, '-R')) __AMF_Log('FileDelete = ' & FileDelete($sAMF_File)) EndIf Else __AMF_Log('Attrib R not found => repeat attempt') Sleep($iInterval) $i -= 1 ContinueLoop EndIf ReDim $aFiles[UBound($aFiles) + 1] $aFiles[0] += 1 $aFiles[UBound($aFiles) - 1] = $sFile __AMF_Log('added to array => repeat attempt on next file') $i -= 1 Next __AMF_Log('iteration ended. files found = ' & $bFound) Until Not $bFound FileClose($hSearch) __AMF_Log('search for submitted files ended') __AMF_Cleanup($sAMF_Path) Return SetExtended(__AMF_Log('end _AcceptMultipleFiles => return array with @extended=' & $aFiles[0], $aFiles[0]), $aFiles) EndFunc ;==>_AcceptMultipleFiles ; #INTERNAL_USE_ONLY# =========================================================================================================== ; __AMF_CreateGUID ; __AMF_TakeOver ; __AMF_MasterIsActive ; __AMF_SubmitFile ; __AMF_Cleanup ; __AMF_Log ; =============================================================================================================================== Func __AMF_CreateGUID($sString) ; ref: https://www.autoitscript.com/forum/topic/147995-createguidfromstring-convert-a-string-to-a-valid-guid/ Return StringRegExpReplace(StringToBinary($sString) & "0000000000000000000000000000000000", "..(.{8})(.{4})(.{4})(.{4})(.{12}).*", "\{$1-$2-$3-$4-$5\}") EndFunc ;==>__AMF_CreateGUID Func __AMF_TakeOver($sAMF_Path, $sAMF_MasterFile) __AMF_Log('enter __AMF_TakeOver') Local $aResult = DllCall('kernel32.dll', 'bool', 'CreateDirectoryW', 'wstr', $sAMF_Path, 'struct*', 0) If @error Or ($aResult[0] = 0) Then Return __AMF_Log('cannot create folder => this instance is not the master. return 0') Else __AMF_Log('FileWriteLine = ' & FileWriteLine($sAMF_MasterFile, @AutoItPID)) __AMF_Log('FileSetAttrib +R = ' & FileSetAttrib($sAMF_MasterFile, '+R')) Return __AMF_Log('master file created => return 1', 1) EndIf EndFunc ;==>__AMF_TakeOver Func __AMF_MasterIsActive($sAMF_MasterFile, $iAttempts, $iInterval) __AMF_Log('enter __AMF_MasterIsActive') Local $iPID For $i = 1 To $iAttempts If StringInStr(FileGetAttrib($sAMF_MasterFile), 'R') Then $iPID = Number(FileReadLine($sAMF_MasterFile)) ExitLoop Else Sleep($iInterval) EndIf Next __AMF_Log('read PID = ' & $iPID) If $iPID = 0 Then Return __AMF_Log('PID=0 => return 0') If ProcessExists($iPID) Then Return __AMF_Log('process exists => return 1', 1) Return __AMF_Log('end __AMF_MasterIsActive => return 0') EndFunc ;==>__AMF_MasterIsActive Func __AMF_SubmitFile($sAMF_Path, $sFile) __AMF_Log('enter __AMF_SubmitFile') Local $sAMF_File = $sAMF_Path & '\PID_' & @AutoItPID __AMF_Log('FileDelete = ' & FileDelete($sAMF_File)) __AMF_Log('file to submit: ' & $sFile) __AMF_Log('FileWriteLine = ' & FileWriteLine($sAMF_File, $sFile)) __AMF_Log('FileSetAttrib +R = ' & FileSetAttrib($sAMF_File, 'R')) __AMF_Log('end __AMF_SubmitFile') EndFunc ;==>__AMF_SubmitFile Func __AMF_Cleanup($sAMF_Path) __AMF_Log('enter __AMF_Cleanup') __AMF_Log('dir to remove: ' & $sAMF_Path) __AMF_Log('FileSetAttrib -R = ' & FileSetAttrib($sAMF_Path, '-R', 1)) __AMF_Log('DirRemove = ' & DirRemove($sAMF_Path, 1)) __AMF_Log('end __AMF_Cleanup') EndFunc ;==>__AMF_Cleanup Func __AMF_Log($sString, $xRet = 0) If $__g_AMF_bDebug Then FileWriteLine(@TempDir & '\AMF_log_' & @AutoItPID & '.txt', @HOUR & ':' & @MIN & ':' & @SEC & '.' & @MSEC & ' (' & @AutoItPID & ') ' & $sString) Return $xRet EndFunc ;==>__AMF_Log Testing Instructions: 1) save the UDF as file "AMF.au3". 2) save the example script as file "AMF_Example.au3". 3) compile the example script as "AMF_Example.exe". 4) place the executable in the root of drive D (or anywhere else ,but adapt the following registry entry accordingly) 5) save this text as registry file "AMF_Example.reg" and import it: Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\*\shell\AMF Example] "MultiSelectModel"="Player" [HKEY_CLASSES_ROOT\*\shell\AMF Example\command] @="D:\\AMF_Example.exe \"%1\"" 6) open Windows Explorer, select multiple files, right click one of them and choose "AMF Example" from the context menu. 7) enjoy! ioa747 and NassauSky 2 Signature - my forum contributions: Spoiler UDF: LFN - support for long file names (over 260 characters) InputImpose - impose valid characters in an input control TimeConvert - convert UTC to/from local time and/or reformat the string representation AMF - accept multiple files from Windows Explorer context menu DateDuration - literal description of the difference between given dates Apps: Touch - set the "modified" timestamp of a file to current time Show For Files - tray menu to show/hide files extensions, hidden & system files, and selection checkboxes SPDiff - Single-Pane Text Diff Link to comment Share on other sites More sharing options...
NassauSky Posted May 11 Share Posted May 11 I like this option but can't seem to get it to work. Maybe since I'm using Windows 11. I created 3 text files and when I select all 3 files, I right click on any of them and choose the new context menu option. It asks me which application to open it in multiple times. I was creating a tiny app to right click on multiple files and have them zip them with my own password. Currently using my app during testing it to an array as it seems you are doing, it opens my array up 3 times (1 instance for each file selected) I was hoping your solution would just open a single instance of an array with all 3 files listed. 😕 Link to comment Share on other sites More sharing options...
NassauSky Posted May 11 Share Posted May 11 Update: @orbs Got it and I wasn't paying attention to the reg file which needed the full path of where I installed the .exe. Also I needed to make sure all backslashes where doubled up. Thanks! Link to comment Share on other sites More sharing options...
Macrostop Posted November 21 Share Posted November 21 Hello @orbs thank you very much for your UDF. Works also well with Autoit v3.3.14.2 Best regards M. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now