2

I am making a API for Batch. One of its functions is a colortext, which uses PowerShell (and findstr if PowerShell is not supported on the machine) to do its work. (It simply converts the batch color given, like 3C, converts it to PowerShell-friendly names, like DarkCyan and Red, and prints the text given in color using more things.)

However, when I try to set the foreground as F (White), for some reason it tries to execute it 2 or 4 times (depending if I have Delayed Expansion enabled or not).

It sometimes fails on the last run/try showing the same thing if the supposed %fcn% and %bcn% never were applied. (this also depends if I have Delayed Expansion enabled or not).

I only want this to execute one time (and not fail for whatever reason), but it's executing 2 or 4 times for some reason. How would I do/fix this program?

Here's the minimal code needed to reproduce this problem, assuming %bcn%, %fcn% and %text% are already set (let's say Cyan as %bcn% and White as %fcn%, and Should not happen as %text%):

@echo off
set bcn=Cyan
set fcn=White
set text="This should not work"
setlocal EnableDelayedExpansion
set script=write-host %text% -ForegroundColor %fcn% -BackgroundColor %bcn%
if EXIST colortest.ps1 ( del colortest.ps1 ) 
echo %script% >> colortest.ps1
@echo on
@powershell -executionpolicy remotesigned -file colortest.ps1
@echo off

The output of this becomes:

non-working function

If I do a color that works (like CD), with the same text:

working function

EDIT: Since no one seems to say "Hey it's working perfectly", I meant about the second bogus execution, I know this works fine, I'm just trying to get rid of the bogus second execution.

Here is the entire code of the batch file (save it as bapi.bat and try to run "bapi colortext "This should not work" BF") in a pastebin:

http://pastebin.com/WyayVUVS

halfer
  • 19,824
  • 17
  • 99
  • 186
770XP
  • 21
  • 3
  • The question is, where the unexpected `(` comes from. Not a solution, but anyway: 1. no need to check for existence before deleting, simply suppress error message if file not found, like: `del colortest.ps1 > nul`; 2. no need to delete file at all in case you do not append (`>>`) but overwrite (`>`), like: `echo %script% > colortest.ps1`; – aschipfl Dec 28 '16 at 11:51
  • Works perfectly happily for me as posted, assuming that the values of the variables `bcn`, `fcn` and `text` are as you describe and not set to some other value (would have helped if you'd set the various test values in the "minimal code needed to reproduce this problem" so we wouldn't be forced to assume anything) – Magoo Dec 28 '16 at 13:20
  • "I am making a API for Batch." Why? I would recommend switching to PowerShell (it is installed by default in Windows 7/Server 2008 R2 and later). – Bill_Stewart Dec 28 '16 at 15:36
  • @Bill_Stewart Uh... the problem is specifically with powershell, as i include it here. – 770XP Dec 28 '16 at 17:48
  • I just realized it was beacuse of something completely unrelated was in a different syntax (call dopws instead of call :dopws, even though the others worked fine with the : sign). – 770XP Dec 28 '16 at 18:02
  • The point is you don't need to create an API _for_ batch files. Use PowerShell _instead of_ batch files. – Bill_Stewart Dec 28 '16 at 19:14
  • PowerShell is rarely used, hard to learn, and unsupported on computers as new as 2007, while Batch has been used since the DOS days (basically, 1980's). It also can be slow (batch takes 1 millisecond per command, powershell can take 600). This API works if you have findstr (supported since 1999), as it also uses it in case powershell is not available. – 770XP Dec 28 '16 at 20:28
  • Rarely used: Not any more (look at the number of PowerShell-tagged questions on SO). Hard to learn: Not as hard as batch files, which can be very arcane (PowerShell syntax is much more consistent). Can be slow: Yes, and batch can also be slow (this is too subjective). – Bill_Stewart Dec 28 '16 at 20:50
  • I found how to stop the unexpected error, lhad to add a line containing ">nul". – 770XP Dec 28 '16 at 20:57

2 Answers2

3

The problem can't be reproduced with the posted batch code and the values for the environment variables.

However some suggestions as partly also posted by aschipfl in his comment.

1. Deletion of a single file

The deletion of a single file can be done with either

if exist colortest.ps1 del colortest.ps1

or with

del colortest.ps1 2>nul

The first command line checks first if the file to delete exists and runs command DEL only if the file really exists.

The second command line runs the command DEL and redirects an error message written to handle STDERR to device NUL to suppress it. Command DEL outputs an error message if the file to delete does not exist at all.

On running just a single command after an IF or in a FOR loop it is really not necessary to write this command line in round brackets which define a command block. FOR and IF are designed for execution of a single command and using ( ... ) which defines a command block is just an extension of Windows command processor to be able to run multiple commands where a single command is expected by design.

2. Redirection to file with overwriting existing file

It is possible here to use just > instead of >> to create or overwrite colortest.ps1. This avoids the need to separately delete the file before.

See also the Microsoft article Using command redirection operators.

3. Space character between output text and redirection operator

On the command line

echo %script% >> colortest.ps1

there is a space character between environment variable reference %script% and redirection operator >>. This space character is also output by command ECHO and therefore also written into the file as trailing space.

This does not matter here, but often trailing spaces are not wanted in produced file. One solution is removing the space character and use:

echo %script%>> colortest.ps1

But this can result in an unexpected behavior if the string of environment variable script ends with a space and a single digit number in range of 1 to 9 as this results after preprocessing this command line before execution in 1>> colortest.ps1 or 2>> colortest.ps1, ... which is a redirection of handle 1 to 9 to file colortest.ps1 instead of printing 1 to 9 to handle STDOUT and finally to the file.

The solution is writing the redirection first and next the command

>>colortest.ps1 echo %script%

or use delayed expansion which would be here even better in case of script PowerShell script line contains special characters.

echo !script!>>colortest.ps1

The space character between >> and file name colortest.ps1 is ignored by Windows command interpreter.

4. Quoting parameter strings with special characters

The environment variable text could hold a text which requires double quoting this parameter string for correct processing by Windows command interpreter and by PowerShell. A space character (delimiter on command line) and the characters &()[]{}^=;!'+,`~<>| require often enclosing the entire parameter string in double quotes for being interpreted completely as literal text.

set "script=write-host "!text!" -ForegroundColor %fcn% -BackgroundColor %bcn%"

See also answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? why set "script=script line" is used instead of just set script=script line and why it does not matter how many double quotes are specified in the script line on assigning it to the environment variable.

5. Double quotes to output in text in PowerShell script file

To output also text containing 1 or more double quotes by the PowerShell script it is necessary to escape each double quote character with one more " before writing the text string into the script file colortest.ps1.

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "bcn=Cyan"
set "fcn=White"
set "text="This should not happen^^!""
set "text=!text:"=""!"
set "script=write-host "!text!" -ForegroundColor %fcn% -BackgroundColor %bcn%"
echo !script!>colortest.ps1
echo on
@powershell -executionpolicy remotesigned -file colortest.ps1
@echo off
endlocal

It can be seen here on this example that with delayed expansion already enabled on definition of text with a string containing also an exclamation mark to output as character additionally to the also to output two double quotes that the exclamation mark must be escaped with two caret characters ^^ for assigning the exclamation mark as literal character to environment variable text. That would not be necessary on definition of text before enabling delayed expansion.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "text="This should not happen!""
setlocal EnableDelayedExpansion
set "bcn=Cyan"
set "fcn=White"
set "text=!text:"=""!"
set "script=write-host "!text!" -ForegroundColor %fcn% -BackgroundColor %bcn%"
echo !script!>colortest.ps1
endlocal
echo on
@powershell -executionpolicy remotesigned -file colortest.ps1
@echo off
endlocal

For details about the commands SETLOCAL and ENDLOCAL see this answer which explains in detail how these two commands work.

Community
  • 1
  • 1
Mofi
  • 46,139
  • 17
  • 80
  • 143
  • @770XP Well, everything posted here works like the batch code posted by you in the question. The error message because of unexpected `(` is caused by a mismatch of round brackets in your entire batch code which you did not post. So we can't really help you to solve your problem. We can give you only the advices above and the advice to remove all `echo off` or comment them out and run the batch file from within a command prompt window to see the processed lines and where exactly the error occurs resulting in exiting the batch processing because of this syntax error. – Mofi Dec 28 '16 at 18:02
  • I actually tried to do that and it just seems to do "==9 (" at the end and the previous line has the "( is unexpected" error. I have no idea why. – 770XP Dec 28 '16 at 18:11
  • No idea why, but i can not show it by adding a line that simply has ">nul". To the bugged CMD, it will just do the command but dissipate/remove the error. – 770XP Dec 28 '16 at 20:58
  • This is a really great answer, thanks for your work @Mofi! – halfer Dec 29 '16 at 19:58
  • Given the remarkable effort in this answer @770XP, I am sure you can muster a better response than "it doesn't work". – halfer Dec 29 '16 at 20:00
  • @770XP your `==9` probably comes from an `IF` statement where you're checking the content of some variable. It seems that your variable is empty at that moment (or only contains whitespaces). Empty variables in `IF` statements cause unexpected behaviour if not surrounded with double quotes. So replace your `IF %var%==9` with `IF "%var%"=="9"` (replace `var` with the name of your variable). For the rest you should really thank @Mofi for the great advices given the poor code you provided that couldn't even reproduce the scenario you were describing. – J.Baoby Jan 04 '17 at 11:04
0

This is because, for some random reason which didn't happen to the rest, the call command executed 2 times, the second without setting any %fcn% and %bcn% parameters, when something like call :label was used.

However, this does not happen with goto label.

halfer
  • 19,824
  • 17
  • 99
  • 186
770XP
  • 21
  • 3
  • Do you mean that you used in your real batch code absolutely correct `call :dopws` to run the code below the jump label `dopws` as subroutine and you have just forgotten to insert above the line `:dopws` a line with `goto :EOF` or `exit /B` to avoid a fall through to subroutine code once the processing of the subroutine finished and therefore the script execution continues below the command line `call :dopws`? By the way: for safety it is advisable to make sure that each subroutine ends also with `goto :EOF` or `exit /B` in case of adding later one more subroutine. – Mofi Dec 28 '16 at 18:13
  • It's in a if statement, and it doesn't escape as a exit function is already built in in the function. – 770XP Dec 28 '16 at 18:28