Call subroutine where parameter contains ampersand in batch file

The batch escape rules are quite nasty, but the behavior is totally predictable if you know the rules.

The info you need to understand the problem is available at How does the Windows Command Interpreter (CMD.EXE) parse scripts? in phases 1, 2, 5, and 6 of the accepted answer. But good luck absorbing that info any time soon :-)

There are two fundamental design issues that lead to your problem: - Phase 6 doubles all carets, which then restarts phase 2 (actually phases 1, 1.5 and 2). - But phase 2 requires & to be escaped as ^&. Note it must be a single ^, not doubled!

The only way to get your approach to work is to introduce the ^ after the phase 6 caret doubling has occurred.

@echo off
setlocal enableDelayedExpansion
set "ESC=^"

rem Calling with delayed expansion value
set "val=with%%ESC%%&ampersand"
call :Output !val!

rem Calling with a string literal
call :Output with%%ESC%%^&ampersand

exit /b

:Output
set "line=%1"
echo Called: !line!
goto :eof

ESC is defined to hold ^.
The first round of phase 1 expands %%ESC%% to %ESC%
the second round of phase 1 (initiated by phase 6) expands %ESC% to ^

This is all totally impractical, especially if you don't know what the content is going to be.

The only sensible strategy to reliably pass any value into a CALLed routine is to pass by reference. Pass the name of a variable that contains the string value, and expand that value within your subroutine using delayed expansion.

@echo off
setlocal enableDelayedExpansion
set "val=with&ampersand"
call :Output val
exit /b

:Output
set "line=!%~1!"
echo Called: !line!
exit /b