visual-studiofor-loopbatch-filedouble-quotesbrackets

Escaping brackets with VSWHERE and a for loop in batch file


This might be similar to Escaping brackets in a for loop in batch file, but there are issues with the delimiters used in the FOR command which are causing problems.

This one has been challenging me, and I've reached an impasse. I need to call VSWHERE to set an environment variable in batch, telling me where Visual Studio 2017 is installed.

The VSWHERE.EXE app which is part of the Visual Studio deployment, which is used to provide details about the various installations of Visual Studio. If you have more than 1 installed, this can help identify what version it is, and other various things, like where exactly its installed.

MS even has their own wiki on how to call VSWHERE in a batch file with the FOR loop. But it lacks details. https://github.com/microsoft/vswhere/wiki

Further to this, to restrict the version, you are supposed to use the following format with brackets and parenthesis -version "[15.0, 16.0)" For Visual Studio 2017, is version 15. So installation versions from 15.0 up to but less than 16.0. Reference: https://github.com/Microsoft/vswhere/issues/86

Where I'm up to. This piece of test batch. The echo's aren't really important, just trying to get it work with the various quotes, brackets, and parenthesis causing it to break.

set "wherecmd="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -version ^"[15.0,16.0^)^" -requires Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites -property installationPath"

echo TESTING CMD
echo %wherecmd%
echo TESTING OUTPUT
%wherecmd%

ECHO TESING FOR
for /f "usebackq tokens=* delims= " %%i in (`%wherecmd%`) do (
set LOCALVS2017PATH=%%i
)

ECHO TESTING VS2017 location (if it exists)
ECHO %LOCALVS2017PATH%

This currently displays the error:

" -requires Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites -property installationPath`) do ( was unexpected at this time.

Any help would be appreciated.


Solution

  • There are two modifications needed to get the batch file working.

    The first one is the definition of the environment variable wherecmd with the following command line:

    set "wherecmd="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -version "[15.0,16.0)" -requires Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites -property installationPath"
    

    See also: Why is no string output with 'echo %var%' after using 'set var = text' on command line? The referenced answer explains in full details why it is possible to define an environment variable with enclosing the argument string for command SET in " with a string value containing itself also ". There are just a few rare use cases mainly those with &<>| where special syntax is needed because of the Windows Command Processor cmd.exe parses the entire command line first before its internal command SET is executed to define the environment variable.

    The command line used in the question defines the environment variable wherecmd with the string value:

    "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -version ^"[15.0,16.0)" -requires Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites -property installationPath
    

    It can be seen here that the first ^ is interpreted by cmd.exe not as escape character on parsing the entire command line before executing its internal command SET. The reason is that the Windows Command Processor itself on parsing a command line does not support " inside an argument string enclosed in ". For cmd.exe itself the command line is interpreted as list of following argument strings:

    1. set
    2. "wherecmd="
    3. C:\Program
    4. Files
    5. (x86)\Microsoft
    6. Visual
    7. Studio\Installer\vswhere.exe
    8. " -version ^"
    9. [15.0,16.0^)^"
    10. -requires
    11. Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites
    12. -property
    13. installationPath"

    The first caret character is inside a double quoted argument string for cmd.exe on parsing the entire command line and is therefore interpreted as literal character and not as escape character. See also: How does the Windows Command Interpreter (CMD.EXE) parse scripts?

    The character ^ causes a syntax error in the used FOR /F command line after replacing %wherecmd% by the wrong encoded command line string assigned to the environment variable as the command line is split up into the arguments lists:

    1. for
    2. /f
    3. "usebackq tokens=* delims= "
    4. %i
    5. in
    6. (`‍
    7. "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
    8. -version
    9. ^"[15.0,16.0)
    10. " -requires Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites -property installationPath`) do (

    The output syntax error message should be clear now on looking on last argument string of the FOR command line without" at the end caused by the wrong ^ in the command line.

    The second line to modify is the FOR /F command line:

    for /F "tokens=* delims= " %%i in ('^"%wherecmd%^"') do set "LOCALVS2017PATH=%%i"
    

    The command line to execute does not contain the character '. The usage of back quotes is therefore not necessary at all.

    FOR /F with a command line defined inside ' on not using back quotes runs on execution one more command process with %ComSpec% /c and the specified command line as additional arguments in background and captures the output to handle STDOUT for processing it next line by line after started cmd.exe closed itself.

    How the arguments after the cmd option /C (run command line and close) or /K (run command line and keep running) are interpreted by cmd.exe depends on multiple criteria explained by the usage help of the Windows Command Processor output on running cmd /? in a command prompt window.

    In this case is the first argument after the cmd option /C the fully qualified file name of an executable which must be enclosed in " because of the spaces and the round brackets in path. The entire command line contains also round brackets. That results in cmd.exe is interpreting the command line as one argument string which is enclosed entirely in " like on cmd option /S would be used although there is no " at the end and removing for that reason the first double quote before executing the remaining command line. The remaining command line is now wrong quoted as it becomes:

    C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -version "[15.0,16.0)" -requires Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites -property installationPath
    

    It is necessary for that reason to enclose the entire command line in " for cmd.exe started in background but without causing a wrong parsing of the entire FOR command line by cmd.exe processing the batch file. The solution is the usage of ^" at beginning and at the end of the command line. cmd.exe processing the batch file interprets the two escaped " as literal characters and the command line between correct quoted as now also the background started command process which is executed with:

    C:\WINDOWS\system32\cmd.exe /c ""C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -version "[15.0,16.0)" -requires Microsoft.Component.MSBuild Microsoft.Net.Component.4.7.TargetingPack Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites -property installationPath"
    

    The usage of the FOR /F options tokens=* delims= enclosed in " to get the two spaces and the two equal signs interpreted literally and not as argument string separators by FOR results in removal of all leading normal spaces from any non-empty captured line and assigning the remaining line as is to the specified environment variable i if not beginning with the default end of line character ;.

    It could be enough depending on the output of vswhere.exe to use just "delims=" to define an empty list of string delimiters turning off the default line splitting behavior or using "tokens=* delims=" with no normal space at the end which is a very little bit faster on execution than just "delims=" with same result which does not remove leading spaces from the line.