Escaping strings when using wmic

前端 未结 2 674
终归单人心
终归单人心 2020-12-11 09:38

I\'m trying to sart an instance of VLC using wmic I\'m doing this mainly because I want to capture the pid of the created process

I know the command below works fine

2条回答
  •  醉梦人生
    2020-12-11 10:12

    Quoting and escaping rules are complicated enough in CMD, and then they get even crazier when you add WMIC complications.

    WMIC can generally use ' or " as quote characters. Double quote within double quotes can be escaped using \". Single quote within single quotes can also be escaped using \', but the backslash does not appear to get consumed, so it seems to be useless.

    CMD only uses ", and there is no way to escape a " within a quoted string. Poison characters like &, |, <, etc. that are not within quotes must be escaped like ^& etc.

    Trying to merge the quoting and escaping rules for both WMIC and CMD is tricky. Remember that the CMD quoting and escaping takes place before WMIC ever sees the command line.

    Also, you could use PROCESS CALL CREATE instead of PROCESS CREATE. I suppose it is possible with PROCESS CREATE, but I've never seen how it is done.

    I'm not sure I know your actual command line that works without using WMIC. Based on your code, I'm assuming the following would work:

    C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"
    

    If so, then I believe the following WMIC command will work:

    wmic process call create 'C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"'
    

    WMIC must see the entire command line after create as one continuous string. The internal single quotes in 'vlc_117/video.mpg' do not cause a problem because there are no spaces in the content. Adding spaces would break this solution, and another strategy would be needed.

    You should be able to use the long path instead of short path if you use double quotes around the exe path:

    wmic process call create '"C:\Program Files\VideoLAN\VLC_117\vlc.exe" rtsp://abcd --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"'
    

    You might want to capture the PID in a variable within a batch file, in which case a FOR /F command would be used. This adds even more complexity. Unquoted CMD token delimiters like =, , etc. must be escaped because of an extra layer of parsing that FOR /F introduces. Unquoted poison characters must also be escaped because of the extra layer of parsing.

    for /f "tokens=2 delims=;= " %%N in (
      'wmic process call create '"C:\Program Files\VideoLAN\VLC_117\vlc.exe" rtsp://abcd --sout^="#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show"' ^| find "ProcessId"'
    ) do set "pid=%%N"
    

    The enclosing single quotes within the FOR /F IN() clause do not cause a problem because they are stripped before WMIC ever sees the command. But the unquoted = in --sout=... must be escaped.

    I'm not in a position to test any of the above, so perhaps none of it works as written. However, the concepts I discuss should still be valid.

    Seemingly minor changes to the command line could have a major impact on the solution because of the many layers and complexities of quoting and escaping. If the command is not as I interpreted, then let me know the correct command by editing your question, and I can try to adapt the answer.


    UPDATE 2014-05-27

    Bad news on my end. The PROCESS CALL CREATE option expects the full command line as the first string argument, optionally followed by the working directory as a second argument. The bad news is the arguments are delimited by commas. Your command line also has commas, and the PROCESS CALL CREATE parser (whatever that is) doesn't seem to support commas within your command line :(

    I've tried quoting the commas within single or double quotes, and that doesn't help. I've also tried escaping the commas with \. Again, no luck. I've also googled for a solution and come up empty.

    I'm afraid there may not be a solution, unless there is VLC syntax available that eliminates the commas, or if there is a way to put the complex VLC command line arguments in some type of external script file. Or maybe some other clever person has discovered a way to escape the commas?

    Here are some examples that show what I have tried. I don't have VLC so I simply substituted CMD /C with an ECHO statement. Trying to ECHO a comma fails every time.

    @echo off
    :: All of these commands without commas work just fine
    wmic process call create 'cmd /c echo hello world^&pause'
    wmic process call create 'cmd /c "echo hello world&pause"'
    wmic process call create "cmd /c echo hello world&pause"
    
    :: But none of these commands with commas work
    wmic process call create 'cmd /c echo hello,goodbye^&pause'
    wmic process call create 'cmd /c "echo hello,goodbye&pause"'
    wmic process call create "cmd /c echo hello,goodbye&pause"
    wmic process call create 'cmd /c echo hello\,goodbye^&pause'
    wmic process call create 'cmd /c "echo hello\,goodbye&pause"'
    wmic process call create "cmd /c echo hello\,goodbye&pause"
    

    Update 2014-12-23: A solution has been found!

    Over at DosTips a group of us developed various methods for a batch script to determine its own PID. Once this is known, it is possible to use WMIC to list all child processes of the parent session. With careful bookkeeping, it is possible to reliably determine the PID of each newly created child process. But it does take a considerable amount of code. This should only be needed if your command line cannot be passed through WMIC PROCESS CALL CREATE (the comma problem).

    @echo off
    setlocal enableDelayedExpansion
    
    :: Get the PID and UID for this batch process
    call :getMyPID
    
    :: Initialize an existing PIDs list
    set "PIDs= "
    
    :: Get a list of all existing child processes, except for the
    :: child CMD.EXE process that was created by this FOR /F loop.
    :: This is necessary because the console session that this script
    :: is running in might have previously created a child process.
    for /f %%A in (
      '2^>nul wmic process where "ParentProcessID=%myPID% and not CommandLine like '%%<%UID%>%%'" get ProcessID'
    ) do for %%B in (%%A) do set "PIDs=!PIDs!%%B "
    
    :: Create your new process as you normally would.
    :: For this demonstration, I will simply create a new CMD.EXE session 
    start
    
    :: Get the PID of the newly created child process by getting all child PIDs,
    :: except for the child CMD.EXE process that was created by this FOR /F loop.
    :: Ignore any PID that already exists in the %PIDs% list. The only PID left
    :: is your new process.
    for /f %%A in (
      '2^>nul wmic process where "ParentProcessID=%myPID% and not CommandLine like '%%<%UID%>%%'" get ProcessID'
    ) do for %%B in (%%A) do if "!PIDs: %%B =!" equ "!PIDs!" set "PID=%%B"
    
    :: At this point you could append the new PID to the PIDs list using
    ::   set "PIDs=!PIDs!%PID% "
    :: Then you can repeat the steps of creating a new proces and getting the new PID
    ::
    :: But instead, I will simply show the result and exit
    echo new PID=%PID%
    
    exit /b
    
    
    :getMyPID 
    setlocal disableDelayedExpansion
    
    :getLock
    
    :: Establish a nearly unique temp file name using %time%
    set "lock=%temp%\%~nx0.%time::=.%.lock"
    
    :: Transform the full path into a UID that can be used in a WMIC query
    set "uid=%lock:\=:b%"
    set "uid=%uid:,=:c%"
    set "uid=%uid:'=:q%"
    set "uid=%uid:_=:u%"
    setlocal enableDelayedExpansion
    set "uid=!uid:%%=:p!"
    endlocal & set "uid=%uid%"
    
    
    :: Establish an exclusive lock on the temp file
    :: If this fails, then loop back and try again until success
    :: This guaranees no two process will ever have the same UID
    2>nul ( 9>"%lock%" (
    
      %= The FOR /F loops creates a child CMD.EXE process which in turn invokes WMIC.         =%
      %= The child CMD.EXE process contains the WMIC query with the UID in its command line.  =%
      %= The WMIC query identifies the CMD.EXE process with the UID in the command line,      =%
      %= and returns the parent process ID, which happens to be the PID for this batch script =%
      for /f "skip=1" %%A in (
        'wmic process where "name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID'
      ) do for %%B in (%%A) do set "PID=%%B"
      (call ) %= Guarantee success so we don't accidently loop again =%
    
    ))||goto :getLock
    
    :: Delete the temp file
    del "%lock%" 2>nul
    
    :: Return the result
    ( endlocal
      set "myPID=%PID%"
      set "UID=%uid%"
    )
    exit /b
    

提交回复
热议问题