Escaping strings when using wmic

2020-02-13 01:45发布

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~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"

(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~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" '

Reliably gives me the same error:

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

However the following:

wmic process create 'C:\PROGRA~1\VideoLAN\VLC_117\vlc.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?

标签: cmd wmic
2条回答
不美不萌又怎样
2楼-- · 2020-02-13 02:07

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
查看更多
别忘想泡老子
3楼-- · 2020-02-13 02:27

I thought I might post another few observations here. PowerShell seems to be more resistant to confusion of tokens and symbols when using WMI to create a process. Just backtick escape nested quotation marks. Example:

$addr = "rtsp://abcd"
$cmd = "cmd /k echo C:\PROGRA~1\VideoLAN\VLC_117\vlc.exe $addr --sout=`"#duplicate{dst=display, dst={std{access=file, mux=ps, dst='vlc_117/video.mpg'}}} --rtsp-caching=120 --no-video-title-show`""
$proc = ([wmiclass]'win32_process').create($cmd)

$proc.ProcessId

Windows Script Host (JScript and VBScript) are equally resistant to the comma problem. Here's a Batch + JScript hybrid script (saved with a .bat extension) to demonstrate:

@if (@CodeSection == @Batch) @then
@echo off & setlocal

set "addr=rtsp://abcd"
for /f %%I in ('cscript /nologo /e:JScript "%~f0" "%addr%"') do set "PID=%%I"
echo Process: %PID%
goto :EOF

@end // end Batch / begin JScript hybrid code
var wmi = GetObject("winmgmts:root\\cimv2"),
    proc = wmi.Get("Win32_Process").Methods_("Create").InParameters.SpawnInstance_();

proc.CommandLine = 'cmd /k echo C:\\PROGRA~1\\VideoLAN\\VLC_117\\vlc.exe '
    + WSH.Arguments(0)
    + ' --sout="#duplicate{dst=display, dst={std{access=file, mux=ps, dst=\'vlc_117/video.mpg\'}}} --rtsp-caching=120 --no-video-title-show"';

var exec = wmi.ExecMethod('Win32_Process', 'Create', proc);
WSH.Echo(exec.ProcessId);

For both the PowerShell and the JScript solutions, remove cmd /k echo to put your vlc command into production.


RE: pure Batch, I can't offer much for the uncommonly complicated vlc command referenced in the question, but this might help others. For less complex commands that need embedded quotation marks but don't have to deal with commas, you can embed quotation marks in a wmic command by escaping them with backslashes.

wmic process call create "powershell \"test-connection localhost -count 1\""

If that doesn't work in your situation, you can also try putting the whole value into a variable retrieved with delayed expansion. Example:

set "proc=powershell \"test-connection localhost -count 1\""
setlocal enabledelayedexpansion
for /f "tokens=2 delims==;" %%I in (
    '2^>NUL wmic process call create "!proc!" ^| find "ProcessId"'
) do endlocal & set /a PID = %%I
echo Proc: %PID%

Or for convenient reuse, you can put it into a subroutine:

@echo off & setlocal

set "pc=localhost"
call :run PID "powershell -window normal \"test-connection %pc% -count 1\""
echo Proc: %PID%
goto :EOF

:run <varname_to_return_PID> <command>
setlocal
set proc=%*
call set proc=%%proc:%1 =%%
setlocal enabledelayedexpansion
for /f "tokens=2 delims==;" %%I in (
    '2^>NUL wmic process call create !proc! ^| find "ProcessId"'
) do (endlocal & endlocal & set /a %~1 = %%I && goto :EOF)

Unfortunately, neither of these pure Batch solutions solves the comma problem.

查看更多
登录 后发表回答