Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
633 views
in Technique[技术] by (71.8m points)

cmd - Escaping strings when using wmic

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 from the command line:

C:PROGRA~1VideoLANVLC_117vlc.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"

(where rtsp://abcd can be any input file for the purposes of this example)

Trying to run it via wmic, with various different attempts as escape sequences (of which the below is one):

wmic process create 'C:PROGRA~1VideoLANVLC_117vlc.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" '

Reliably gives me the same error:

Invalid format.
Hint: <assignlist> = <propertyname>=<propertyvalue> [, <assignlist>].

However the following:

wmic process create 'C:PROGRA~1VideoLANVLC_117vlc.exe rtsp://abcd --sout="#duplicate{dst=display} --rtsp-caching=120 --no-video-title-show"'

Works fine - except that as a command it is useless to me. So the problem seems to be with the nested curly bracketed section of my original command.

I've tried various different escape characters...so far with no success. Can anyone suggest where I am going wrong?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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~1VideoLANVLC_117vlc.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~1VideoLANVLC_117vlc.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 FilesVideoLANVLC_117vlc.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 FilesVideoLANVLC_117vlc.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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...