3

I want to put a user inputted string which contains an ampersand into the clipboard using batch. I can modify the string, and I can get it to print to the terminal using setlocal EnableExtensions EnableDelayedExpansion
but I can't pipe it to the clipboard.

There is an in depth discussion here which talks about why pipes can break things, but I couldn't understand it well enough to get around my problem. https://www.robvanderwoude.com/battech_inputvalidation_setp.php

setlocal EnableExtensions EnableDelayedExpansion

set /P "INPUT=Paste the stuff in the terminal please" 
set "SEARCHTEXT=+"
set REPLACETEXT=%%2B

for /F "delims=" %%A in ("%INPUT%") do (
    set "string=%%A"
    set "modified=!string:%SEARCHTEXT%=%REPLACETEXT%!"
    echo !modified! | clip
)

Because the string I'm trying to modify contains "&username" in it, the output I get is: 'username' is not recognized as an internal or external command, operable program or batch file.

If I only echo !modified!, there are no errors. How can I get an arbitrary un-sanitized string into the clipboard?

aschipfl
  • 33,626
  • 12
  • 54
  • 99
Max Kessler
  • 55
  • 1
  • 8
  • 1
    What happens when you replace `echo !modified! | clip` with `cmd /V /C echo(^^!modified^^!| clip`? – aschipfl Jul 24 '19 at 21:16
  • It works, and the correct thing is in my clipboard now. I have no idea what that command means, but thank you! – Max Kessler Jul 24 '19 at 21:21
  • So, it opens a new instance of cmd, with delayed expansion, and then runs exactly the same thing I typed before, and then closes? I don't understand why this works, especially since I thought that piping with | already opens another instance of cmd? Why does the new terminal not have the same problem? – Max Kessler Jul 24 '19 at 21:33
  • I tried to explain the approach in an [answer](https://stackoverflow.com/a/57191882) -- please let me know whether everything is clear now... – aschipfl Jul 24 '19 at 22:10

1 Answers1

4

The major problem in your code is the following line:

echo !modified! | clip

A pipe (|) creates a new cmd instance for either side. You have got delayed expansion enabled in your script, so the variable !modified! becomes expanded when the whole command line is parsed, then the pipe is executed, and then the new cmd instance for the left side receives the variable already expanded, including all potential poisonous characters, like &, for example.

To prevent !modified! to be expanded immediately, we need to escape the exclamation marks like ^^! (^^ becomes first escaped to a single ^, so ^! is left during the delayed expansion phase), which lets the ! be treated as a literal character and no variable expansion happens at first.

The new cmd instance (for the left side of the pipe in our situation) now has got delayed expansion disabled, so we need to explicitly instantiate another (nested) one with delayed expansion enabled (by cmd /V):

cmd /V /C echo(^^!modified^^!| clip

With this technique we force the variable !modified! to be expanded as late as possible, hence by the inner-most cmd instance, which avoids the expanded string to be received by any other instance, and therefore, poinsonous characters become hidden from the parser.


In addition, I used the safe echo variant echo(..., because echo ... might fail under certain circumstances (imagine ... is the literal string /?). Moreover, I removed the SPACE in front of |, because such would become echoed as well, unintentionally.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • Let me see if understand this right. Are you saying the pipe receives the unexpanded string because the pipe command is enclosed in the cmd command? I'm trying to figure out why the pipe command in the new cmd instance doesn't create yet another cmd instance which receives the bad input. – Max Kessler Jul 25 '19 at 12:56
  • Are you making it so that the string is expanded after the pipe happens? – Max Kessler Jul 25 '19 at 12:59
  • 1
    The `cmd` instsance implicitly created by the pipe receives the *already expanded value* of your variable, that is the major problem; my variant lets the inner-most `cmd` instance receive the *variable name* (`!modified!`), that is the clue here; so yes, the string becomes expanded *after* the pipe is executed... – aschipfl Jul 25 '19 at 13:01
  • When I run the command like this to try and see what the inner command line receives: start cmd /V /K echo(^^!modified^^!| clip It seems the prompt has received the modified string. Also it doesn't work when I add start. Is there a way for me to determine on my own exactly when the pipe to the clipboard happens? Is there a way for me to see that instance of cmd in a new terminal? – Max Kessler Jul 25 '19 at 18:14
  • Well, I don't know how to show what `cmd` instance receives what, and I don't think there's a way to open a new window for each instance. How did you insert `start` exactly? I guess `start` won't help here as it'll likely create another `cmd` instance... – aschipfl Jul 25 '19 at 21:55
  • I just appended start, like so: `start cmd /V /K echo(^^!modified^^!| clip` I did some tests while looking in task manager, and I don't think start creates yet another cmd instance. I think what start does is ensure that whatever program you run gets its own terminal. The terminal for the inner cmd instance shows the expanded string, and nothing else, but adding start also makes the script not work, so its not useful as a debugging tool in this case. – Max Kessler Jul 26 '19 at 14:37