5

I am trying to log the output of net stop while also capturing its ERRORLEVEL.

Based on this question, I attempted the following from within a nested subroutine:

set /a loopIndex=0
for /F "usebackq delims=" %%i in (`net stop %SERVICE_NAME%`) do (
 if !loopIndex! EQU 0 if !errorlevel! EQU 1 set statementError=1
 set /a loopIndex+=1
 call :logMessage "%%i"
)
echo statementError: %statementError%

However, this does not work, throwing 1 even when net stop succeeds.

Is this possible without a temp file? If not, what would a temp file solution look like?

Community
  • 1
  • 1
srage
  • 990
  • 1
  • 9
  • 27
  • ERRORLEVEL is not an environmental variable. It doesn't need to be surrounded with % or !. `IF ErrorLevel 1` works fine. – Ken White Jul 21 '16 at 22:48
  • Read the linked post again. *In addition to this internal state, **you can, if you wish, create an environment variable with the name ERRORLEVEL**, in the same way that you can create an environment variable called FRED. But, as with FRED, that variable won't have any effect on the error level. * (**emphasis mine**). – Ken White Jul 21 '16 at 22:54
  • 2
    @KenWhite `ERRORLEVEL` *is* an automatic environment variable, just like `CD`, `DATE` or `RANDOM`. – dxiv Jul 21 '16 at 22:59
  • @DrewBeres The control command of a `for /f` loop runs in a separate instance of cmd. Its exit code (or errorlevel) is not available inside the loop itself. – dxiv Jul 21 '16 at 23:00
  • @dxiv Thank you, that is the information I seek. Is it safe to grab it immediately after the loop? – srage Jul 21 '16 at 23:03
  • 3
    @DrewBeres The exit code of the secondary `cmd` process is not available after the loop, either. It's not exactly straightforward to retrieve it, see for example [Re: Get return code from command within FOR loop](http://www.dostips.com/forum/viewtopic.php?t=6933#p45092). P.S. Thanks but this really is a pointer rather than an actual answer, so I'll leave it as a comment. If you use, or build upon, ideas from there, feel free to post the answer yourself. – dxiv Jul 21 '16 at 23:09
  • @KenWhite from `cmd /?`: "If Command Extensions are enabled, then there are several dynamic environment variables that can be expanded but which don't show up in the list of variables displayed by SET. These variable values are computed dynamically each time the value of the variable is expanded. If the user explicitly defines a variable with one of these names, then that definition will override the dynamic one described below." So it appears that my source is using terminology wrong. – srage Jul 21 '16 at 23:13

3 Answers3

3

As @drruruu asked in this question :

Is this possible without a temp file?

Yes, it's possible without a temp file. By sending ERRORLEVEL to STDOUT in the IN clause and parse it in the LOOP clause. And it could be done with delayed expansion too.

For convenience, here is an example. It's somewhat a FINDSTR wrapper that search for a string in the batch itself. It covers all the common cases where you need to know what was going wrong, where and why :

  • Error in the DO () clause (aka the loop) and get the corresponding exit code
  • Error in the IN () clause and get the corresponding exit code
  • Error directly at the FOR clause (wrong syntax, bad delimiters, etc.)

The following script simulates theses situations with FINDSTR and flags as parameters :

  • The first parameter is the string to search.
  • The second parameter is a 0/1 flag to simulate an error not related to FINDSTR in the loop.
  • The third parameter is a way to simulate an error on the FOR clause itself (not on IN nor LOOP)
  • The fourth parameter is a way to test a FINDSTR which exit 255 when the file to search does not exist. Sadly, FINDSTR exit with 1 when it can't find a string in the file/files, but also exit with 1 when it can't find any files.. With the fourth parameter, we simulate a situation where FINDSTR exit with 255 when it can't find the file.
    @echo off
    SETLOCAL ENABLEEXTENSIONS
    IF ERRORLEVEL 1 (
      ECHO Can't use extensions
      EXIT /B 1
    )    
    SETLOCAL ENABLEDELAYEDEXPANSION
    IF ERRORLEVEL 1 (
      ECHO Can't use delayed expansion 
      EXIT /B 1
    )
    REM The string to search
    SET "LOCALV_STRING=%1"
    REM The file to search. Myself.
    SET "LOCALV_THIS=%0"
    REM Store the exit code for the LOOP
    SET "LOCALV_ERR="
    REM Store the exit code for the IN
    SET "LOCALV_RET="
    REM Flag to stop parsing output for error simulation
    SET "LOCALV_END="
    REM To get the exit code of the IN clause, we get it through expansion with a second FOR loop using the well known CALL expansion and send it on STDOUT in the form "__<code>"
    FOR /F "%~3tokens=*" %%M IN ('FINDSTR "!LOCALV_STRING!" "!LOCALV_THIS%~4!" ^
                               ^& FOR /F %%A IN ^("ERRORLEVEL"^) DO @CALL ECHO __%%%%A%%') DO (
      SET "LOCALV_TMP=%%~M"
      REM Simulate that something goes wrong with FINDSTR I/O
      IF NOT EXIST "!LOCALV_THIS!%~4" ( 
        SET "LOCALV_RET=255"
        SET LOCALV_END=1
      )
      IF "!LOCALV_END!" == "" (
        REM SImulate a problem in the loop
        IF "%2" == "1" (
          (CMD /C EXIT /B 127)
          SET LOCALV_END=1
        ) ELSE (
          IF NOT "!LOCALV_TMP:~0,2!" == "__" ECHO Found: !LOCALV_TMP!
        )
      )
      IF "!LOCALV_TMP:~0,2!!LOCALV_RET!" == "__" SET "LOCALV_RET=!LOCALV_TMP:__=!"
    )
    SET "LOCALV_ERR=!ERRORLEVEL!"
    REM LOCALV_ERR get the exit code from the last iteration of the for loop
    REM LOCALV_RET get the exit code from the IN command of the for loop
    REM Sadly, FINDSTR exit with 1 if it did not find the string, but also with 1 if it could not found the file. To simulate a proper handling of exit code for 
    REM abnormal hardware/software situation, %2 is used to force a 255 exit code
    REM If LOCALV_RET is not defined, this means the FOR...ECHO__.. wasn't executed, therefore there is a pb with the FOR LOOP
    IF "!LOCALV_RET!" == "" (
      ECHO Something went wrong with FOR...
      EXIT /B 1
    )
    REM If LOCALV_RET is defined, this means the FOR...ECHO__.. was executed and the last loop operation has parsed the FINDSTR exit code, LOCALV_RET get its exit code
    REM If LOCALV_RET is defined but LOCALV_ERR is not "0", something went wrong in the loop (I/O error, out of memory, wathever you could think), the problem is not FINDSTR
    IF NOT "!LOCALV_ERR!" == "0" (
      ECHO Error in the loop while searching "!LOCALV_STRING!" in "!LOCALV_THIS!", exit code !LOCALV_RET!. Loop exit code : !LOCALV_ERR!.
      EXIT /B 4
    )
    REM If LOCALV_RET is "0", FINDSTR got matching strings in the file, if "1", FINDSTR don't find any matching string, if anything else, FINDSTR got a problem like failed I/O.
    REM If LOCALV_RET is "0" and LOCALV_ERR is "0", everything is ok.
    IF "!LOCALV_RET!" == "0" (
      ECHO Success.
      EXIT /B 0
    )
    REM If LOCALV_RET is "1" and LOCALV_ERR is "0", FINDSTR failed to find the string in the file "or" failed to find file, for the latter we simulate that FINDSTR exit with 255 .
    IF "!LOCALV_RET!" == "1" (
      ECHO FINDSTR failed to find "!LOCALV_STRING!" in "!LOCALV_THIS!", exit code !LOCALV_RET!. Loop exit code : !LOCALV_ERR!.
      EXIT /B 2
    )
    REM If LOCALV_RET isn't "0" nor "1" and LOCALV_ERR is "0", FINDSTR failed to do the job and LOCALV_RET got the exit code.
    ECHO FINDSTR: Houst^W OP, we've got a problem here while searching "!LOCALV_STRING!" in "!LOCALV_THIS!", exit code !LOCALV_RET!. Loop exit code : !LOCALV_ERR!.
    EXIT /B 3

Script output :

  1. Normal operation, no error simulation.
PROMPT>.\for.bat FOR 0 "" ""
Found: FOR /F "%~3tokens=*" %%M IN ('FINDSTR "FOR" ""
Found: ^& FOR /F %%A IN ^("ERRORLEVEL"^) DO @CALL ECHO __%%%%A%%') DO (
Found: REM If LOCALV_RET is not defined, this means the FOR...ECHO__.. wasn't executed, therefore there is a pb with the FOR LOOP
Found: ECHO Something went wrong with FOR...
Found: REM If LOCALV_RET is defined, this means the FOR...ECHO__.. was executed and the last loop operation has parsed the FINDSTR exit code, LOCALV_RET get its exit code
Success.
  1. Normal operation, no error simulation, with a string that FINDSTR can't find in the file.
PROMPT>.\for.bat ZZZ 0 "" ""
FINDSTR failed to find "ZZZ" in ".\for.bat", exit code 1. Loop exit code : 0.
  1. Simulate an error in the LOOP clause, not related to FINDSTR.
PROMPT>.\for.bat FOR 1 "" ""
Error in the loop while searching "FOR" in ".\for.bat", exit code 0. Loop exit code : 127.
  1. Simulate an error in the FOR clause at start with an unknow "delimstoken" option.
PROMPT>.\for.bat FOR 0 "delims" ""
delimstokens=*" was unexpected.
Something went wrong with FOR...
  1. Simulate FINDSTR exiting 255 if it can't find the file.
PROMPT>.\for.bat FOR 1 "" "ERR"
FINDSTR : Can't open
FINDSTR: HoustW OP, we've got a problem here while searching "FOR" in ".\for.bat", exit code 255. Loop exit code : 0.
Zilog80
  • 2,534
  • 2
  • 15
  • 20
2

The FOR /F command executes NET STOP in a new cmd.exe process. FOR /F processes stdout, but that is it. There is no way for the main script to see any variable values that the FOR /F command might create, as they are gone once the sub-process terminates.

The simplest and most efficient solutions use a temporary file. I'm assuming NET STOP has two possible error codes - Success = 0, and Error = 1. So the simplest solution is to simply create a temporary error signal file if there was error.

The following demonstrates the concept in a generic way:

@echo off
del error.flag 2>nul
for /f "delims=" %%A in ('net stop %SERVICE_NAME% ^|^| echo error>error.flag') do (
  ...
)
if exist error.flag (
  echo There was an error
  del error.flag
)

You could just as easily put the error test within the DO() code if desired.

dbenham
  • 127,446
  • 28
  • 251
  • 390
2

While @dbenham's answer is suitable for cases where %ERRORLEVEL% returns a binary value, I was not able to confirm or deny if the returned exit codes for net stop are in fact binary and so opted for an n-ary solution.

As per @dbenham's DOS tips forum post:

FOR /F "delims=" %%i IN ('net stop MyService 2^>^&1 ^& CALL ECHO %%^^ERRORLEVEL%%^>error.level') DO (
 CALL :logMessage "%%i"
)
FOR /F "delims=" %%i IN (error.level) DO (SET /A statementError=%%i)
DEL error.level
IF %statementError% NEQ 0 ()

Breaking down the statement parsing:

net stop MyService 2^>^&1 ^& CALL ECHO %%^^ERRORLEVEL%%^>error.level
net stop MyService 2>&1 & CALL ECHO %^ERRORLEVEL%>error.level
echo %ERRORLEVEL%>error.level

Here, CALL is used specifically to delay parsing of %ERRORLEVEL% until execution of ECHO.

srage
  • 990
  • 1
  • 9
  • 27
  • Since file `error.level` contains a single line only, you could read it like `set /P statementError="" < "error.level"`, or, in case the file might not exist or be empty, `set "statementError=0" & set /P statementError="" < "error.level" 2> nul`... – aschipfl Jul 13 '17 at 20:40