Search code examples
sqlbatch-filescheduled-taskssqlcmd

Running sqlcmd in batch file works but running the same batch file as a scheduled task works and does nothing


I have looked at many SO questions/answers and though some seem similar to my issue they do not seem to be. The answers given fix issues the questions were asking about but will not solve my issue.

I have a batch file...

@ECHO ON

ECHO Disabling the following... >> C:\App\Debug.log
ECHO - V1 >> C:\Apps\Debug.log

FOR /F "tokens=* USEBACKQ" %%F IN (`sqlcmd -j -S DOMAIN\SQLSERVER -U username -P password -d DBNAME -Q "UPDATE [DBNAME].[dbo].[table1] SET ColOne='V1_OFF' WHERE ColOne='V1'"`) DO (
        Echo %%F >> C:\Apps\Debug.log
)
EXIT /B

When I run this file at the command prompt it works perfectly fine. When I run it as a scheduled task it show me the echos but nothing for the for loop as expected.

  1. Yes I have made sure the username (using whoami) is the same for the scheduled task set up as the manual run that I do.
  2. Yes I know the user running the script has rights to everything (file access as well as DB access) because it works fine running it from the command prompt.
  3. Scheduled task is set to run wither user is logged on or not.

Any ideas what might be wrong or what I can try for debugging purposes? Thanks!


Solution

  • sqlcmd is perhaps not enough. cmd.exe in environment of scheduled task may fail to find the executable using local PATHEXT and local PATH environment variables. The executable should be specified with full qualified file name, i.e. drive + path + name + extension. Then the batch file does not anymore depend on the environment variables PATH and PATHEXT because of all files are referenced with full qualified file name.

    for executes the specified command line with starting in background one more command process with %ComSpec% /c and the specified command line appended. This means executed is following with Windows installed on drive C::

    C:\Windows\System32\cmd.exe /c sqlcmd -j -S DOMAIN\SQLSERVER -U username -P password -d DBNAME -Q "UPDATE [DBNAME].[dbo].[table1] SET ColOne='V1_OFF' WHERE ColOne='V1'"
    

    for captures everything written to handle STDOUT of started command process. The lines of captured output are processed line by line by for after started cmd.exe terminated itself. Error messages output by started cmd.exe or the commands/executables executed by Windows command processor in background to handle STDERR are redirected to handle STDERR of command process processing the batch file and printed to console. But there is no console window on running a batch file as scheduled task. So error messages cannot be seen in this case.

    The for command line can be modified easily here to get also error messages written into the C:\Apps\Debug.log.

    FOR /F "tokens=* USEBACKQ" %%F IN (`sqlcmd -j -S DOMAIN\SQLSERVER -U username -P password -d DBNAME -Q "UPDATE [DBNAME].[dbo].[table1] SET ColOne='V1_OFF' WHERE ColOne='V1' 2^>^&1"`) DO (
    

    The Microsoft article Using command redirection operators explains 2>&1. The two operators > and & must be escaped with ^ to be interpreted as literal characters on Windows command processor parsing the for command line before executing finally for which executes next %ComSpec% /c with the specified command line on which 2^>^&1 is changed already to 2>&1.

    Does the log file C:\App\Debug.log contain with this modification following two lines?

    'sqlcmd' is not recognized as an internal or external command,
    operable program or batch file.

    Yes, then no executable with file name sqlcmd is found by started cmd.exe. The best solution is referencing this executable with full qualified file name. See also: What is the reason for "X is not recognized as an internal or external command, operable program or batch file"?

    Otherwise sqlcmd outputs perhaps an error message which should be now also in the log file C:\App\Debug.log.

    It would be also possible to use following command line to let background cmd.exe write the error messages into a separate error log file C:\App\Error.log:

    FOR /F "tokens=* USEBACKQ" %%F IN (`sqlcmd -j -S DOMAIN\SQLSERVER -U username -P password -d DBNAME -Q "UPDATE [DBNAME].[dbo].[table1] SET ColOne='V1_OFF' WHERE ColOne='V1'" 2^>C:\App\Error.log`) DO (
    

    "tokens=* usebackq" results in first deleting all leading horizontal tabs and normal spaces on non-empty lines by for, then checking if the remaining line starts with ; in which case the line is also ignored and finally assigning the captured line not starting with ; and with leading tabs/spaces removed to loop variable F for further processing.

    Better would be using the options usebackq^ delims^=^ eol^= not enclosed in double quotes which requires escaping the two spaces and the two equal signs with caret character ^ to be interpreted as literal characters by cmd.exe on parsing the command line before executing for. The line splitting behavior is disabled completed with delims= because of the definition of an empty list of delimiters. And no line except an empty line is ignored anymore because of end of line character modified from default ; to no character.

    Finally a space on an echo line left to redirection operator >> is also output by echo and for that reason written as trailing space into the log file. Therefore no space should be used left to > or >> on printing a line with echo redirected into a file. But care must be taken on omitting the space character left to the redirection operator. The word left to redirection operator should not be 1, 2, ..., 9 as this would result in redirecting the output to these numbered handles into the specified file instead of the character 1, 2, etc. So if unknown text should be written into a file, it is better to specify first the redirection operator > or >> and the full qualified file name and next the echo command with the text to output. See also: Why does ECHO command print some extra trailing space into the file?

    The three command lines with echo would be for this batch file:

    ECHO Disabling the following...>> C:\App\Debug.log
    ECHO - V1>> C:\Apps\Debug.log
    
    >>C:\Apps\Debug.log ECHO %%F
    

    following... is safe for being correct written into the file as also V1. %%F could be just 1 or a string ending with a space and a single digit and so it is better to specify the redirection first on the last echo command line to get finally executed by cmd.exe the command line ECHO %%F 1>>C:\Apps\Debug.log.