12

I have a console application. Interaction with this application is done via TCP/IP.

I also have a test framework for it, which is basically a collection of BATCH scripts (...not my fault). What this test framework does for each test is basically this:

  1. start /min "myapplication.exe" and wait until verification is received that the application is up and running.
  2. send commands via TCP/IP to this application, receive its replies, and check if the timings and values agree with whatever is expected by the specific test.

One problem that I'm currently encountering is that the application exits prematurely due to some internal error. I would like to distinguish between failed tests, and the application crashing. The one and only indication I have for this, is the application's exit code.

So, I tried the following:

start /min cmd /c "myapplication.exe || echo %errorLevel% > exitcode.txt"

and then later on in the test scripts,

if exist exitcode.txt (
    set /p exitcode=<exitcode.txt
    echo ERROR: myapplication.exe returned exitcode %exitcode%.
    goto error
) else (
    goto do_processing
)

but for some strange reason, the text file never appears, even though I sometimes get a dialog about the application crashing, and even though I forcibly make it fail with a known non-zero exit code. The test just goes through do_processing and (of course) results in failure.

EDIT When I run

start /min cmd /c "nonsense || echo %errorLevel% > test.txt"

I sometimes get a text file containing the string 9009, but other times that text file contains the string 0, or sometimes 1, ...What the...?!

EDIT2 If you type

cmd /k "nonsense || echo %errorLevel%"

(note the /k option), you see 0 being printed in the new window, but if you then type echo %errorlevel%, you get 1....

I knew batch was not very sane, but it should at least be consistently insane...

Any ideas on what could be going on here?

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96
  • You'll have to use the /wait option to get an exit code. Which ought to defeat the point of using start in the first place. Avoid fire-and-forget when error checking is required. – Hans Passant Feb 04 '15 at 10:40
  • @Hans Passant: I think this won't work because if you use `start` the the exit code will not be passed to the calling script. You should use `call` instead. It behaves like `start /wait` but passes variables. Check this: http://stackoverflow.com/questions/13257571/call-command-vs-start-with-wait-option – MichaelS Feb 04 '15 at 10:44
  • @HansPassant: but `start /wait` means the script execution will block. The script can then not go on to fire commands at it... – Rody Oldenhuis Feb 04 '15 at 10:48
  • @MichaelS I called start (to be able to background the task) in combination with cmd (to be able to issue 2 commands; the executable, plus the echo that outputs to file). – Rody Oldenhuis Feb 04 '15 at 10:55

3 Answers3

17

Normal expansion like %errorLevel% occurs when the statement is parsed, and the entire CMD /C command line is parsed in one pass, so the value you get is the value that existed before the commands were run (always 0).

You can get a more precise explanation at https://stackoverflow.com/a/4095133/1012053. It can be difficult to follow and understand the significance of the phases at first, but it is worth the effort.

To solve your problem you must delay expansion of the variable until after your exe has run.

You have two options:

Option 1) Use CALL to get a delayed round of expansion.

In a batch file you would double the percents. But the commands run using CMD /C are run under a command line context, not batch. Doubling the percents does not work under the command line.

Instead, you must introduce a caret (cmd.exe escape character) into the variable name. The first phase of expansion occurs before escapes are processed, so it looks for a name with the caret, and doesn't find it. When not found, the command line parser preserves the original text when the variable is not found. Next the special characters are handled and the escape is consumed. So when the CALL round of expansion occurs, it sees the correct variable name.

start /min cmd /c "myapplication.exe || call echo %^errorLevel% > exitcode.txt"

I believe you are issuing the START command within a batch script, so you must also double the percents to prevent the parent batch script from expanding ERRORLEVEL.

start /min cmd /c "myapplication.exe || call echo %%^errorLevel%% > exitcode.txt"

Option 2) Use delayed expansion

Delayed expansion syntax is !errorlevel! instead of %errorlevel%. But before you can use it, delayed expansion must be enabled. In a batch script you would use setlocal enableDelayedExpansion, but that doesn't work in a command line context. Instead, you must use the cmd.exe /v:on option.

Assuming your batch script has not enabled delayed expansion, then you would simply use the following:

start /min cmd /v:on /c "myapplication.exe || echo !errorLevel! > exitcode.txt"

But if your batch script has enabled delayed expansion, then you must escape the ! so that the parent batch script does not expand ERRORLEVEL. Note that you must still use /v:on because the STARTed sub-process (normally) defaults to disabled delayed expansion.

start /min cmd /v:on /c "myapplication.exe || echo ^!errorLevel^! > exitcode.txt"
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • AAAAAARGH! I can't believe I fell for this once again!! This solves it nicely, thank you very much. Ugh, to think of the headaches this has caused in so many people... – Rody Oldenhuis Feb 04 '15 at 11:45
  • just out of curiosity: is there any advantage/disadvantage of `call` vs. `cmd /v:on`? – Rody Oldenhuis Feb 04 '15 at 11:49
  • 1
    In this case, not really. CALL is comparatively very slow, but that only comes into play when doing many iterations within a tight loop - not an issue here. CALL also wreaks havoc on quoted carets that are supposed to pass through (doubles them), again not an issue. Delayed expansion corrupts expansion of FOR variables whose value contains `!` character, also not a problem here. – dbenham Feb 04 '15 at 11:54
  • Something is still not quite going as it should...when the condition occurs (application crashes), the text file is written in both options you gave. However, with option 1) it contains "`ECHO is on.`", and with option 2) it contains "`0`"...not sure if it matters, but the `start` is called from within an `if (...)`, which resides in a batch file, which is `called` from another batch file, that has `setlocal EnableDelayedExpansion` – Rody Oldenhuis Feb 04 '15 at 16:50
4

As explained here you have to use call instead of start to be able to evaluate the exit codes. call will launch the script in the same variable environment whereas start will run it in a new one which is not accessable from the first script.

Community
  • 1
  • 1
MichaelS
  • 5,941
  • 6
  • 31
  • 46
  • ...but the exit code is written to a *file* in the same process, so I don't have to care about the specific variable environment... – Rody Oldenhuis Feb 04 '15 at 10:51
  • You can write the exit code with `@echo %errorlevel% >> output.txt`. And this is the reason why you can't use `start`. `start` won't set a accessable %errorlevel% in the calling script. Using `start` lets you execute the test in background but you will never get access to the exit code. – MichaelS Feb 04 '15 at 10:56
  • Oh, sorry, I've missunderstood you. – MichaelS Feb 04 '15 at 10:58
  • That's why I use `start /min cmd /c "myapplication.exe || echo %errorLevel% > exitcode.txt"`. Start then starts up a `cmd`, with its own variable environment, which runs the application and (conditionally) outputs any non-zero exit code to file. – Rody Oldenhuis Feb 04 '15 at 10:58
  • Yes, sorry, the original title was perhaps misleading. – Rody Oldenhuis Feb 04 '15 at 10:59
  • Have you tried to use `start /min cmd /c "myapplication.exe & IF NOT %errorlevel%==0 echo %errorLevel% > exitcode.txt"` instead? – MichaelS Feb 04 '15 at 11:08
  • I'll try, although I won't see how this would be different...shouldn't the `if not %errorlevel%==0` be essentially equal to what the `||` operator does? – Rody Oldenhuis Feb 04 '15 at 11:10
  • I'm not sure. I've just tried the following line: `start calc || echo test` and it didn't print test but the errorlevel of `calc` is 9009. – MichaelS Feb 04 '15 at 11:14
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70210/discussion-between-rody-oldenhuis-and-michaels). – Rody Oldenhuis Feb 04 '15 at 11:16
1

Another solution may be to use & instead of ||

start myapplication.exe ^& echo %errorLevel% ^> exitcode.txt

The ^ is an escape char, so that this is evaluated inside the start and not outside as explained here.

This worked for me. Hope it helps someone.

AxelWass
  • 1,321
  • 12
  • 21