Search code examples
batch-filefor-loopcmd

FOR loop spaces and handling


I have a batch script that lists all services,takes the BINARY_PATH, removes any lines that contain c"\windows" and provides a list of BINARY_PATHS.

The idea is to then pass that list into ICACLS to determine the permissions set on each of those executables.

The problem I have is that some BINARY_PATHS contain leading and trailing ". So I have had to account for this by adding the delims=" to my for /f statement.

Below is the batch file that outputs to ECHO

for /f "tokens=2" %%n in ('sc query state^= all ^| findstr SERVICE_NAME') do (
  for /f "delims=: tokens=1*" %%r in (
    'sc qc "%%~n" ^| findstr BINARY_PATH_NAME'
  ) do (
    for /f tokens^=1-2^ delims^=^" %%x in ('echo %%~s^| findstr /V /I "c:\windows\system32"') do (
         echo "%%~x%%~y" 
        )
  )
)

The ECHO output is as following - a nice clean list it would seem apart from the leading space

" C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_state.exe"
" C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorsvw.exe"
" C:\Windows\Microsoft.Net\Framework\v3.0\WPF\PresentationFontCache.exe"
" C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\infoc
ard.exe"
" C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\SMSvc
Host.exe"
" C:\Program Files\Photodex\ProShow Producer\ScsiAccess.exe"
" C:\Windows\servicing\TrustedInstaller.exe"
" C:\Program Files\VMware\VMware Tools\vmtoolsd.exe"

When I try and pass the output to icacls without the leading and trailing ", it breaks down because there are spaces in some of the PATHs.

ICACLS batch file:

for /f "tokens=2" %%n in ('sc query state^= all ^| findstr SERVICE_NAME') do (
  for /f "delims=: tokens=1*" %%r in (
    'sc qc "%%~n" ^| findstr BINARY_PATH_NAME'
  ) do (
    for /f tokens^=1-2^ delims^=^" %%x in ('echo %%~s ^| findstr /V /I "c:\windows\system32"') do (
         icacls %%~x%%~y 
        )
  )
)

Some output from ICACLS batch:

C:\Windows\Microsoft.Net\Framework\v3.0\WPF\PresentationFontCache.exe NT SERVICE
\TrustedInstaller:(F)
                                                                      BUILTIN\Ad
ministrators:(RX)
                                                                      NT AUTHORI
TY\SYSTEM:(RX)
                                                                      BUILTIN\Us
ers:(RX)

Successfully processed 1 files; Failed processing 0 files
Invalid parameter "Communication"
Invalid parameter "Communication"
Invalid parameter "Files\Photodex\ProShow"
C:\Windows\servicing\TrustedInstaller.exe NT SERVICE\TrustedInstaller:(F)
                                          BUILTIN\Administrators:(RX)
                                          NT AUTHORITY\SYSTEM:(RX)
                                          BUILTIN\Users:(RX)

Successfully processed 1 files; Failed processing 0 files
Invalid parameter "Files\VMware\VMware"

Any ideas?


Solution

  • You are absolutely right to enclose paths in between quotation marks in order to avoid trouble with white-spaces. But instead of removing and leading and trailing spaces which are also contained within the "", I would try to not produce them rather than to eliminate them later.


    The leading space comes from the middle for /f loop:

    for /f "delims=: tokens=1*" %%r in ('sc qc "%%~n" ^| findstr BINARY_PATH_NAME') do ( ... )
    

    The filtered sc qc command produces output lines like:

            BINARY_PATH_NAME   : C:\Windows\System32\spoolsv.exe
            BINARY_PATH_NAME   : "C:\Program Files\iPod\bin\iPodService.exe"
    

    With your "delims=: tokens=1*" option, the part after the delimiter (:) holds a leading space. To overcome this, add the space as another delimiter, but make sure it is the very last character in the option string: "tokens=1* delims=: ".


    The trailing space comes from the piped echo command line parsed by the inner for /f loop:

    for /f tokens^=1-2^ delims^=^" %%x in ('echo %%~s ^| findstr /V /I "c:\windows\system32"') do ( ... )
    

    The space between echo %%~s and ^| is also included in the output. Therefore simply remove it. It is not necessary to define the option string tokens^=1-2^ delims^=^", because the for /f loop should not receive any quotation marks to parse anyway (due to the ~ modifier in %%~s), so "delims=" is enough. Hence use %%~x in the loop body only.
    Furthermore, I suggest to write echo(%%~s instead of echo %%~s in order to avoid ECHO is on/off. messages if %%~s is empty (although this should never happen).


    Another problem is that service names retrieved by sc query state= all may contain white-spaces as well; for example:

    SERVICE_NAME: iPod Service
    

    So for the outer for /f loop, the "tokens=2" option is not optimal; in the above example, only iPod would be returned. Therefore correct it to "tokens=1*" and ignore the first token.


    Since literal strings are searched only, I replaced the findstr instances by find, except the last one, because find supports a single search string only. I added another search string to the remaining findstr instance in order to cover also 64-bit Windows systems where a directory C:\Windows\SysWOW64 also exists, which I assume you want to filter out too.


    Here is the fixed code:

    @echo off
    for /f "tokens=1*" %%m in ('sc query state^= all ^| find "SERVICE_NAME"') do (
        for /f "tokens=1* delims=: " %%r in ('sc qc "%%~n" ^| find "BINARY_PATH_NAME"') do (
            for /f "delims=" %%x in ('echo(%%~s^| findstr /L /V /I /C:"%SystemRoot%\System32" /C:"%SystemRoot%\SysWOW64"') do (
                ECHO icacls "%%~x"
            )
        )
    )
    

    Remove the upper-case ECHO after successful testing in order to execute the icacls command line.