1

I created a file including some macros named myMacrosLib.cmd that I can call from another batch script. Below an example of myMacrosLib.cmd At the begin of every macro there is a label and at the end there is an exit /b so I can load in memory every macro one by one or up to 8 macros in a single command line from another batch script. For doing this I've done another macro as follow: I would like to make this last macro more elegant by transforming the 8 lines into a for loop but I can't do it, probably due to escape characters problems, is there anyone who can help me solve the problem?

:: loadMacroFromLib.bat
@echo on
:: Define LF as a Line Feed (newline) string
set LF=^


::Above 2 blank lines are required - do not remove

::define a newline with line continuation
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

:: pathToLibFile macroName1 macroName2 ... macroName8
:: define a macro to load upto 8 macros in memory from lib file
:: It is to be loaded in memory with a call command all the other macros
:: can be loaded by this macro, 8 by 8!!!
set @{loadMacroFromLib}=for %%. in (1 2) do if %%.==2 (for /f "tokens=1-9 delims= " %%1 in ("!args!") do (%\n%
    endlocal ^& (%\n%
        if defined %%2 call %%%%1%% :%%%%2%% %\n%
        if defined %%3 call %%%%1%% :%%%%3%% %\n%
        if defined %%4 call %%%%1%% :%%%%4%% %\n%
        if defined %%5 call %%%%1%% :%%%%5%% %\n%
        if defined %%6 call %%%%1%% :%%%%6%% %\n%
        if defined %%7 call %%%%1%% :%%%%7%% %\n%
        if defined %%8 call %%%%1%% :%%%%8%% %\n%
        if defined %%9 call %%%%1%% :%%%%9%% %\n%
    ) %\n%
)) else setlocal EnableDelayedExpansion ^& set args=

set "pathToLibFile=.\LIBRERIA\myMacrosLib.cmd"
set "macroName1=@{creaLinkWeb}"
set "macroName2=@{setHostN}"
set "macroName3=@{creaLink}"
%@{loadMacroFromLib}% pathToLibFile macroName1 macroName2 macroName3

I've tried the following but doesn't work!!!

set @{loadMacroFromLib}=for %%. in (1 2) do if %%.==2 (for /f "tokens=1-9 delims= " %%1 in ("!args!") do (%\n%
    endlocal ^& (%\n%
        for /l %%I in (2,1,9) do (%\n%
            if defined %%%%%%I call %%%%1%% :%%%%%%%%I%% %\n%
        )%\n%
    ) %\n%
)) else setlocal EnableDelayedExpansion ^& set args=
:: myMacrosLib.cmd
call %*
exit /B %ERRORLEVEL%

:: Define macro
:: Define CR as Carriage Return
for /f %%C in ('copy /Z "%~dpf0" nul') do set "CR=%%C"

:: Define LF as a Line Feed (newline) string
set LF=^


::Above 2 blank lines are required - do not remove

::define a newline with line continuation
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

:@{clrErr}
::{  Sets the ERRORLEVEL to 0 }
set "@clrErr=(call )"
exit /b

:@{setErr}
::{  Sets the ERRORLEVEL to 1 }
set "@clrErr=(call)"
exit /b

:@{setHostN}
: macro semplice senza parametri di I/O
: (C) by Andrea Rossetti - Ufficio SITEL - Direzione Tecnica - Comune di Spoleto
:: *****************************************************************************
:: * Imposto la variabile HOSTN = a %CLIENTNAME% se siamo in Citrix oppure     *
:: * la imposto = a %COMPUTERNAME% se eseguiamo lo script dal PC locale        *
:: * esempio di chiamata:                                                      *
:: * %@{setHostn}%                                                             *
:: *****************************************************************************
set @{setHostN}=IF NOT DEFINED CLIENTNAME (%\n%
    SET HOSTN=%COMPUTERNAME%%\n%
    ) ELSE (%\n%
    SET HOSTN=%CLIENTNAME%%\n%
)
exit /b

:@{setHostn}
: macro semplice senza parametri di I/O
: (C) by Andrea Rossetti - Ufficio SITEL - Direzione Tecnica - Comune di Spoleto
set @{setHome}=IF NOT DEFINED HOMESHARE (%\n%
    SET HOME=%USERPROFILE%%\n%
    ) ELSE (%\n%
    SET HOME=%HOMESHARE%%\n%
)
exit /b

:@{setCurrDirName}
: macro semplice senza parametri di I/O
: (C) by Andrea Rossetti - Ufficio SITEL - Direzione Tecnica - Comune di Spoleto
:: ************************************************************
:: * Imposto la variabile currDirName = <nome dir corrente>   *
:: * ad esempio se la dir corrente è "c:\pippo\pluto" allora  *
:: * imposto currDirName = "pluto"                            *
:: ************************************************************
set @{setCurrDirName}=for %%I in (%CD%) do set currDirName=%%~nxI %\n%
exit /b

:@{creaLink}
: sLinkFile targetPath workingDirectory iconLocation iconIndex description windowStyle hotKey
: (C) by Andrea Rossetti - Ufficio SITEL - Direzione Tecnica - Comune di Spoleto
: tutte le variabili sono di input dove gli argomenti sono i seguenti:
::   1. il path completo del link tra virgolette "" (%1)
::   2. il path completo dell'eseguibile tra virgolette "" (%2)
::   3. il path della directory di lavoro tra virgolette "" (%3)
::   4. il path completo di un file .ico [ , 0 | , 1 | ... | , <posizione dell'icona nel .ico>] tra virgolette ad esempio "\path\to\file.ico, 0" (%4)
::   5. l'indice dell'icona se il file che la contiene, contiene più icone (%5)
::   6. una descrizione tra virgolette "" (%6)
::   7. windows style [ 1 | 3 | 7 ] tra virgolette "" (%7)
::                    1 = Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position.
::                    3 = Activates the window and displays it as a maximized window. 
::                    7 = Minimizes the window and activates the next top-level window.
::   8. Hotkey ad esempio "Ctrl+Alt+e" (%8)
set @{creaLink}=for %%. in (1 2) do if %%.==2 (%\n%
  for /F "tokens=1-8 delims= " %%1 in ("!argv!") do (%\n%
      if 1==2 (La vera macro inizia sotto)%\n%
      set "script=%TEMP%\%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.vbs" %\n%
      echo Set oWS = WScript.CreateObject^^("WScript.Shell"^^) ^> !script! %\n%
      echo sLinkFile = "!%%1!" ^>^> !script! %\n%
      echo Set oLink = oWS.CreateShortcut^^(sLinkFile^^) ^>^> !script! %\n%
      echo oLink.TargetPath = "!%%2!" ^>^> !script! %\n%
      echo oLink.WorkingDirectory = "!%%3!" ^>^> !script! %\n%
      echo oLink.IconLocation = "!%%4!, !%%5!" ^>^> !script! %\n%
      echo oLink.Description = "!%%6!" ^>^> !script! %\n%
      echo oLink.WindowStyle = "!%%7!" ^>^> !script! %\n%
      echo oLink.Hotkey = "!%%8!" ^>^> !script! %\n%
      echo oLink.Save ^>^> !script! %\n%
      cscript /nologo !script! %\n%
      del !script! %\n%
      if 1==2 (Fine macro)%\n%
      endlocal %\n%
      )%\n%
) else setlocal enableDelayedExpansion^&set argv=
exit /b

:@{creaLinkWeb}
: linkPath url iconFile iconIndex
: (C) by Andrea Rossetti - Ufficio SITEL - Direzione Tecnica - Comune di Spoleto
: tutte le variabili sono di input dove gli argomenti sono i seguenti:
set @{creaLinkWeb}=for %%. in (1 2) do if %%.==2 (%\n%
  for /F "tokens=1-4 delims= " %%1 in ("!argv!") do (%\n%
      if 1==2 (La vera macro inizia sotto)%\n%
      echo [InternetShortcut] ^> !%%1! %\n%
      echo URL="!%%2!" ^>^> !%%1! %\n%
      echo IconFile=!%%3! ^>^> !%%1! %\n%
      echo IconIndex=!%%4! ^>^> !%%1! %\n%
      echo HotKey=0 ^>^> !%%1! %\n%
      echo IDList= ^>^> !%%1! %\n%
      if 1==2 (Fine macro)%\n%
      endlocal %\n%
      )%\n%
) else setlocal enableDelayedExpansion^&set argv=
exit /b

  • 2
    A `for` loop cannot be used to build up argument references like `%2` like that, because the latter are expanded and must therefore be already available *before* `for` meta-variables… – aschipfl Aug 02 '21 at 17:10
  • Not even the "setlocal enabledelayedexpansion" function can help? If yes how can use it? – Andrea Rossetti Aug 02 '21 at 20:23
  • Actually I don't think so, because you can't delay expansion of argument references… – aschipfl Aug 02 '21 at 20:43

2 Answers2

1

For using a loop you could use the line feed variable trick, replacing all spaces by line feeds.
That results in single loops per argument storing it in %%2, instead of only one loop with %%2, %%3, %%4 ...
But therefore I split the first argument from the remaining arguments in the first FOR-loop (for /f "tokens=1,* ...)

set @{loadMacroFromLib}=for %%. in (1 2) do if %%.==2 (for %%L in ("!LF!") DO ( %\n%
    for /f "tokens=1,* delims= " %%1 in ("!args!") do ( %\n%
    set "args=%%2" %\n%
    for /f "tokens=* delims=" %%2 in ("!args: =%%~L!") do (%\n%
        %= The if-expression will only be true, if delayed expansion is enabled =% %\n%
        if "!!" == "" endlocal %\n%
        call %%%%1%% :%%%%2%% %\n%
    ) %\n%
    ) %\n%
)) else setlocal EnableDelayedExpansion ^& set args=

Btw. I don't understand why you are using variables in the first place.

Why not using a direct way like

set "pathToLibFile=.\LIBRERIA\myMacrosLib.cmd"
%@{loadMacroFromLib}% %pathToLibFile% @{creaLinkWeb} @{setHostN} @{creaLink}

Then the inner block of `@{loadMacroFromLib}` macro looks like
    
for /f "tokens=* delims=" %%2 in ("!args: =%%~L!") do (%\n%
    %= The if-expression will only be true, if delayed expansion is enabled =% %\n%
    if "!!" == "" endlocal %\n%
    call %%1 :%%2 %\n%
) %\n%

Btw2. The modern definition of \n is

(set \n=^^^
%=EMPTY=%
)

Your definition is an old style, it produces the same macros, but is more complex.

jeb
  • 78,592
  • 17
  • 171
  • 225
  • Thanks Jeb, for the modern definition, I take note! And then what's the purpose of (for %%L in ("!LF!") DO ( ? I'm sorry but I like to understand very well everything I'm going to do – Andrea Rossetti Aug 03 '21 at 10:06
  • @AndreaRossetti It stores the line feed (with quotes) into %%L, that's necessary to use it later in `!args: =%%~L!`, because you can't expand the line feed in a different way there. Things like `!args: =%LF%!` or `!args: =!LF!!` would fail – jeb Aug 03 '21 at 10:15
  • Excuse me I have another question: how do variables with macro definitions loaded in memory to cross the endlocal barrier? – Andrea Rossetti Aug 03 '21 at 10:21
  • In fact I did a test by running loadMacroFromLib.bat from the command prompt and then typing echo %@{seetHostN}% and it just returns %@{seetHostN}% which means that the variable @{setHostN} is not set, like the others two. – Andrea Rossetti Aug 03 '21 at 10:39
  • @AndreaRossetti It was a construction problem of my code, I moved the `endlocal` into the loop. You could also use a `return out of endlocal scope`, but that's a bit more complex – jeb Aug 05 '21 at 16:40
  • Hi Jeb can you post the final working code please? – Andrea Rossetti Aug 06 '21 at 17:50
  • Jeb, could you explain the technique of `return out of endlocal scope` to me? Thanks. – Andrea Rossetti Aug 06 '21 at 19:24
  • @AndreaRossetti A good question/answer is at [SO: Make an environment variable survive ENDLOCAL](https://stackoverflow.com/q/3262287/463115) – jeb Aug 07 '21 at 13:07
1

Jeb's solution is very elegant and works perfectly, the only problem is that the variables containing the loaded macros do not cross the endlocal barrier. I solved it by prepending the call line "endlocal ^&" in this way the variables that are set by the call command, cross the endlocal barrier.

Thank you very much Jeb!!!

Below the working code:

set @{loadMacroFromLib}=for %%. in (1 2) do if %%.==2 (for %%L in ("!LF!") DO ( %\n%
for /f "tokens=1,* delims= " %%1 in ("!args!") do ( %\n%
        set "args=%%2" %\n%
    for /f "tokens=* delims=" %%2 in ("!args: =%%~L!") do (%\n%
        endlocal ^& call %%%%1%% :%%%%2%% %\n%
    ) %\n%
    ) %\n%
)) else setlocal EnableDelayedExpansion ^& set args=

and then I can load in memory the macros by the following commands:

set "pathToLibFile=.\LIBRERIA\myMacrosLib.cmd"
set "macroName1=@{creaLinkWeb}"
set "macroName2=@{setHostN}"
set "macroName3=@{creaLink}"
%@{loadMacroFromLib}% pathToLibFile macroName1 macroName2 macroName3

Note for Jeb: I can't use

set "pathToLibFile=.\LIBRERIA\myMacrosLib.cmd"
%@{loadMacroFromLib}% %pathToLibFile% @{creaLinkWeb} @{setHostN} @{creaLink}

it returns the following errors:

E:\BATCH>(
set "args=@{creaLinkWeb} @{setHostN} @{creaLink}"
 for /F "tokens=* delims=" %2 in ("!args: =
!") do (endlocal   & call %.\LIBRERIA\myMacrosLib.cmd% :%@{creaLinkWeb} @{setHostN} @{creaLink}%  )
)

E:\BATCH>(endlocal   & call %.\LIBRERIA\myMacrosLib.cmd% :%@{creaLinkWeb}%  )
Impossibile trovare l'etichetta batch specificata - for

E:\BATCH>(endlocal   & call %.\LIBRERIA\myMacrosLib.cmd% :%@{setHostN}%  )
Impossibile trovare l'etichetta batch specificata - IF

E:\BATCH>(endlocal   & call %.\LIBRERIA\myMacrosLib.cmd% :%@{creaLink}%  )
Impossibile trovare l'etichetta batch specificata - for
  • If you use `%@{loadMacroFromLib}% %pathToLibFile% @{creaLinkWeb} @{setHostN} @{creaLink}` you need to simplyfy your `CALL` to `CALL %%1 :%%2 %\n%` – jeb Aug 05 '21 at 16:17
  • Your solution has an endlocal leak, because you call it too often (once per macroName), that can cause unexpected scope errors, when calling the @{loadMacroFromLib} macro. I solved it with `if "!!" == "" endlocal`, that works in your case, because your macro initialization requires `DisabledDelayedExtension` – jeb Aug 05 '21 at 16:29