Search code examples
for-loopbatch-fileterminal-services

Flow control in a batch file


Reference Iterating arrays in a batch file

I have the following:

for /f "tokens=1" %%Q in ('query termserver') do (
    if not ERRORLEVEL (
        echo Checking %%Q
        for /f "tokens=1" %%U in ('query user %UserID% /server:%%Q') do (echo %%Q)
    )
)

When running query termserver from the command line, the first two lines are:

Known
------------------------- 

...followed by the list of terminal servers. However, I do not want to include these as part of the query user command. Also, there are about 4 servers I do not wish to include. When I supply UserID with this code, the program is promptly exiting. I know it has something to do with the if statement. Is this not possible to nest flow control inside the for-loop?

I had tried setting a variable to exactly the names of the servers I wanted to check, but the iteration would end on the first server:

set TermServers=Server1.Server2.Server3.Server7.Server8.Server10

for /f "tokens=2 delims=.=" %%Q in ('set TermServers') do (
    echo Checking %%Q
    for /f "tokens=1" %%U in ('query user %UserID% /server:%%Q') do (echo %%Q)
)

I would prefer this second example over the first if nothing else for cleanliness.

Any help regarding either of these issues would be greatly appreciated.


Solution

  • Again, there are multiple things to note here.

    if errorlevel

    The help for if says:

    IF [NOT] ERRORLEVEL number command
    

    as syntax for the if errorlevel condition. That is, you must provide a number to compare against. Keep in mind that if errorlevel n evaluates to true if the exit code was at least n.

    So

    if errorlevel 1 ...
    

    catches any error (that is signaled through the exit code), while

    if errorlevel 0 ...
    

    simply is always true.

    Anyways, you probably want a

    if not errorlevel 1 ...
    

    here, since that condition is true if no error occurred.

    Skipping lines

    The for /f command has an argument skip=n which can be used to skip lines at the start. If your output starts with two lines you don't want, then you can just do

    for /f "skip=2 tokens=1" %%Q in ('query termserver') do
    

    Iterating over multiple known values in for /f

    The problem with your second code snippet is that for iterates line-wise. So when you give it a single environment variable it will tokenize it (and put the tokens into different variables), but the loop runs only once per line. Also note that using set here is a bit error-prone as you might get more back than you want. Something like

    for /f ... in ("%TermServers%") ...
    

    would have been easier. Still, that doesn't solve the original problem. The easiest way to solve this would probably be something like the following:

    rem space-separated list of servers
    set TermServers=Server1 Server2 Server3 Server7 Server8 Server10
    
    rem call the subroutine with the list of servers
    call :query_servers %TermServers%
    
    rem exit the batch file here, to prevent the subroutine from running again afterwards
    goto :eof
    
    rem Subroutine to iterate over the list of servers
    :query_servers
    
      rem Process the next server in the list
      rem Note the usage of %1 here instead of a for loop variable
      echo Checking %1          
      for /f "tokens=1" %%U in ('query user %UserID% /server:%1') do (echo %%Q)
    
      rem Remove the first argument we just processed
      shift
    
      rem if there is still another server to be processed, then do so
      rem we're mis-using the subroutine label as a jump target here too
      if not [%1]==[] goto query_servers
    
    rem This is kind of a "return" statement for subroutines
    goto :eof
    

    (untested, but should work.)

    ETA: Gah, and once again I miss the most obvious answer:

    set TermServers=Server1 Server2 Server3 Server7 Server8 Server10
    for %%S in (%TermServers%) do (
        for /f "tokens=1" %%U in ('query user %UserID% /server:%1') do (echo %%Q)
    )
    

    Note that this is simply for, not for /f and it will dutifully iterate over a list of values. I don't know how I missed that one, sorry.